@utilix-tech/sdk 0.3.0 → 0.4.0
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/CHANGELOG.md +15 -0
- package/README.md +44 -32
- package/SECURITY.md +16 -0
- package/dist/{chunk-UC656APA.js → chunk-DNCOIO5N.js} +64 -1
- package/dist/{chunk-OKSWDVOM.js → chunk-GX7H6TAX.js} +1 -1
- package/dist/{chunk-W4UBLYFU.js → chunk-HFRTZE7T.js} +2 -2
- package/dist/{chunk-ROTPLW7T.js → chunk-IPR7FSX4.js} +2 -2
- package/dist/{chunk-T3JTY2M5.js → chunk-N7C52YGL.js} +1 -1
- package/dist/{chunk-XST6X3HT.js → chunk-QJE743LY.js} +10 -10
- package/dist/{chunk-3BAHSW4C.js → chunk-V5JKVDBA.js} +60 -1
- package/dist/{chunk-NSOARQCM.js → chunk-V5S6OJ4A.js} +14 -14
- package/dist/{data-rMjWNiOJ.d.cts → data-CakDxAcT.d.cts} +36 -2
- package/dist/{data-rMjWNiOJ.d.ts → data-CakDxAcT.d.ts} +36 -2
- package/dist/index.cjs +152 -30
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -8
- package/dist/{network-CNtmrDeN.d.cts → network-DwyAjwJS.d.cts} +39 -2
- package/dist/{network-CNtmrDeN.d.ts → network-DwyAjwJS.d.ts} +39 -2
- package/dist/tools/api.cjs +2 -2
- package/dist/tools/api.js +1 -1
- package/dist/tools/code.cjs +14 -14
- package/dist/tools/code.js +1 -1
- package/dist/tools/data.cjs +63 -0
- package/dist/tools/data.d.cts +1 -1
- package/dist/tools/data.d.ts +1 -1
- package/dist/tools/data.js +1 -1
- package/dist/tools/encoding.cjs +2 -2
- package/dist/tools/encoding.js +1 -1
- package/dist/tools/media.cjs +1 -1
- package/dist/tools/media.js +1 -1
- package/dist/tools/misc.cjs +1 -1
- package/dist/tools/misc.js +1 -1
- package/dist/tools/network.cjs +59 -0
- package/dist/tools/network.d.cts +1 -1
- package/dist/tools/network.d.ts +1 -1
- package/dist/tools/network.js +1 -1
- package/dist/tools/units.cjs +10 -10
- package/dist/tools/units.js +1 -1
- package/package.json +18 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Full release notes for all Utilix surfaces (browser tools, REST API, SDKs, MCP server) live at [utilix.tech/changelog](https://utilix.tech/changelog). This file tracks just this package.
|
|
4
|
+
|
|
5
|
+
## 0.3.1
|
|
6
|
+
- Metadata fixes: corrected repository/bug tracker links, updated tool count in the package description.
|
|
7
|
+
|
|
8
|
+
## 0.3.0
|
|
9
|
+
- Added the `media` module.
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
- 400+ functions across 14 modules, including the `ai_agent` module for LLM/RAG pipelines.
|
|
13
|
+
|
|
14
|
+
## 0.1.0
|
|
15
|
+
- Initial release.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/sdk
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**140+ developer utility functions for Node.js: runs locally, no API key required.**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@utilix-tech/sdk)
|
|
6
6
|
[](https://www.npmjs.com/package/@utilix-tech/sdk)
|
|
@@ -62,7 +62,7 @@ const { formatJson } = require("@utilix-tech/sdk/json");
|
|
|
62
62
|
|
|
63
63
|
## Modules
|
|
64
64
|
|
|
65
|
-
### `/json
|
|
65
|
+
### `/json`: JSON Tools
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
68
|
import { formatJson, minifyJson, diffJson, jsonToTypescript, validateJson,
|
|
@@ -71,7 +71,7 @@ import { formatJson, minifyJson, diffJson, jsonToTypescript, validateJson,
|
|
|
71
71
|
// Format with 2-space indent
|
|
72
72
|
formatJson('{"name":"alice","age":30}', { indent: 2 });
|
|
73
73
|
|
|
74
|
-
// Diff two JSON strings
|
|
74
|
+
// Diff two JSON strings: returns line-by-line diff
|
|
75
75
|
diffJson(jsonA, jsonB);
|
|
76
76
|
|
|
77
77
|
// Generate TypeScript interface from JSON
|
|
@@ -89,7 +89,7 @@ validateJson(data, schema);
|
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
92
|
-
### `/encoding
|
|
92
|
+
### `/encoding`: Encode & Decode
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
95
|
import { encodeBase64, decodeBase64, encodeUrl, decodeUrl,
|
|
@@ -104,7 +104,7 @@ base32Encode("hello"); // "NBSWY3DPEB3W64TMMQ======"
|
|
|
104
104
|
|
|
105
105
|
---
|
|
106
106
|
|
|
107
|
-
### `/hashing
|
|
107
|
+
### `/hashing`: Hash & Password
|
|
108
108
|
|
|
109
109
|
```ts
|
|
110
110
|
import { hashAll, hashOne, hashPassword, verifyPassword, generateHtpasswdFile } from "@utilix-tech/sdk/hashing";
|
|
@@ -123,7 +123,7 @@ generateHtpasswdFile([{ username: "alice", password: "secret" }]);
|
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
|
-
### `/text
|
|
126
|
+
### `/text`: String & Text
|
|
127
127
|
|
|
128
128
|
```ts
|
|
129
129
|
import { convertCase, slugify, countWords, generateWords, generateParagraphs,
|
|
@@ -152,11 +152,12 @@ detectPassiveVoice("The report was reviewed by the committee. They approved it."
|
|
|
152
152
|
|
|
153
153
|
---
|
|
154
154
|
|
|
155
|
-
### `/data
|
|
155
|
+
### `/data`: CSV / YAML / TOML / XML / INI / NDJSON
|
|
156
156
|
|
|
157
157
|
```ts
|
|
158
158
|
import { parseCsv, csvToJson, validateYaml, tomlToJson, jsonToToml,
|
|
159
|
-
formatXml, xmlToJson, jsonToXml, parseIni
|
|
159
|
+
formatXml, xmlToJson, jsonToXml, parseIni,
|
|
160
|
+
validateNdjson, formatNdjson, ndjsonToJsonArray } from "@utilix-tech/sdk/data";
|
|
160
161
|
|
|
161
162
|
// CSV
|
|
162
163
|
const rows = parseCsv("name,age\nalice,30\nbob,25");
|
|
@@ -175,11 +176,16 @@ xmlToJson("<root><item>hello</item></root>");
|
|
|
175
176
|
|
|
176
177
|
// INI
|
|
177
178
|
parseIni("[db]\nhost=localhost\nport=5432");
|
|
179
|
+
|
|
180
|
+
// NDJSON (JSON Lines): one JSON value per line
|
|
181
|
+
validateNdjson('{"id":1}\n{"id":2}'); // { valid: true, totalLines: 2, ... }
|
|
182
|
+
formatNdjson('{"id":1}'); // '{\n "id": 1\n}'
|
|
183
|
+
ndjsonToJsonArray('{"id":1}\n{"id":2}'); // '[\n { "id": 1 },\n { "id": 2 }\n]'
|
|
178
184
|
```
|
|
179
185
|
|
|
180
186
|
---
|
|
181
187
|
|
|
182
|
-
### `/generators
|
|
188
|
+
### `/generators`: UUID, Passwords, Fake Data
|
|
183
189
|
|
|
184
190
|
```ts
|
|
185
191
|
import { generateUuid, generateV4, generateV7, generateUlid,
|
|
@@ -202,7 +208,7 @@ generateData([
|
|
|
202
208
|
|
|
203
209
|
---
|
|
204
210
|
|
|
205
|
-
### `/time
|
|
211
|
+
### `/time`: Dates, Timezones, Cron
|
|
206
212
|
|
|
207
213
|
```ts
|
|
208
214
|
import { diffDates, parseCron, convertTime, formatRelative,
|
|
@@ -225,7 +231,7 @@ getNextRuns("*/5 * * * *", 5); // next 5 run timestamps
|
|
|
225
231
|
|
|
226
232
|
---
|
|
227
233
|
|
|
228
|
-
### `/units
|
|
234
|
+
### `/units`: Conversions
|
|
229
235
|
|
|
230
236
|
```ts
|
|
231
237
|
import { convertBytes, pxToAll, convert, formatValue } from "@utilix-tech/sdk/units";
|
|
@@ -250,11 +256,12 @@ formatValue(1234567.89, { locale: "en-US", currency: "USD" });
|
|
|
250
256
|
|
|
251
257
|
---
|
|
252
258
|
|
|
253
|
-
### `/network
|
|
259
|
+
### `/network`: URLs, IPs, HTTP, HAR
|
|
254
260
|
|
|
255
261
|
```ts
|
|
256
262
|
import { getStatusCode, isValidIp, isValidIpv4, isValidIpv6,
|
|
257
|
-
searchStatusCodes, buildGeoUrl, countryCodeToFlag
|
|
263
|
+
searchStatusCodes, buildGeoUrl, countryCodeToFlag,
|
|
264
|
+
parseHar } from "@utilix-tech/sdk/network";
|
|
258
265
|
|
|
259
266
|
getStatusCode(404);
|
|
260
267
|
// { code: 404, text: "Not Found", description: "...", category: "Client Error" }
|
|
@@ -266,11 +273,16 @@ isValidIpv6("::1"); // true
|
|
|
266
273
|
isValidIp("256.0.0.1"); // false
|
|
267
274
|
|
|
268
275
|
countryCodeToFlag("US"); // "🇺🇸"
|
|
276
|
+
|
|
277
|
+
// Parse a DevTools .har network export into entries + summary stats
|
|
278
|
+
const har = await fs.promises.readFile("network.har", "utf-8");
|
|
279
|
+
parseHar(har);
|
|
280
|
+
// { version: "1.2", entries: [...], summary: { totalRequests, totalSize, failedRequests, ... } }
|
|
269
281
|
```
|
|
270
282
|
|
|
271
283
|
---
|
|
272
284
|
|
|
273
|
-
### `/api
|
|
285
|
+
### `/api`: cURL, JWT, CORS, HTTP
|
|
274
286
|
|
|
275
287
|
```ts
|
|
276
288
|
import { buildCurl, parseCurlCommand, decodeJwt, signJwt,
|
|
@@ -289,7 +301,7 @@ buildCurl({
|
|
|
289
301
|
// Parse a curl command back into its parts
|
|
290
302
|
parseCurlCommand('curl -X GET https://api.example.com -H "Authorization: Bearer token"');
|
|
291
303
|
|
|
292
|
-
// JWT
|
|
304
|
+
// JWT: decode without verification
|
|
293
305
|
decodeJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...");
|
|
294
306
|
// { header: {...}, payload: {...}, isExpired: false, expiresIn: "2h" }
|
|
295
307
|
|
|
@@ -302,7 +314,7 @@ generateCspHeader({ "default-src": ["'self'"], "script-src": ["'self'", "cdn.exa
|
|
|
302
314
|
|
|
303
315
|
---
|
|
304
316
|
|
|
305
|
-
### `/code
|
|
317
|
+
### `/code`: SQL, HTML, RegEx, GQL
|
|
306
318
|
|
|
307
319
|
```ts
|
|
308
320
|
import { formatSql, minifySql, testRegex, formatHtml, formatGql,
|
|
@@ -334,7 +346,7 @@ formatGql("query { user(id: 1) { name email } }");
|
|
|
334
346
|
|
|
335
347
|
---
|
|
336
348
|
|
|
337
|
-
### `/color
|
|
349
|
+
### `/color`: Color Conversion & Palettes
|
|
338
350
|
|
|
339
351
|
```ts
|
|
340
352
|
import { parseColor, convertColor, generatePalette, checkContrast } from "@utilix-tech/sdk/color";
|
|
@@ -354,7 +366,7 @@ checkContrast("#ffffff", "#000000");
|
|
|
354
366
|
|
|
355
367
|
---
|
|
356
368
|
|
|
357
|
-
### `/css
|
|
369
|
+
### `/css`: CSS Generators
|
|
358
370
|
|
|
359
371
|
```ts
|
|
360
372
|
import { generateGradient, generateBoxShadow, generateBorderRadius,
|
|
@@ -376,7 +388,7 @@ generateBorderRadius({ tl: 8, tr: 8, br: 0, bl: 0 });
|
|
|
376
388
|
|
|
377
389
|
---
|
|
378
390
|
|
|
379
|
-
### `/misc
|
|
391
|
+
### `/misc`: SVG, QR, Unicode
|
|
380
392
|
|
|
381
393
|
```ts
|
|
382
394
|
import { optimizeSvg, sanitizeSvg, analyzeString, toUnicodeEscape,
|
|
@@ -400,13 +412,13 @@ analyzeString("café"); // { length: 4, codePoints: [...], ... }
|
|
|
400
412
|
|
|
401
413
|
---
|
|
402
414
|
|
|
403
|
-
### `/media
|
|
415
|
+
### `/media`: Image Header Parsing
|
|
404
416
|
|
|
405
417
|
```ts
|
|
406
418
|
import { readImageInfo } from "@utilix-tech/sdk/media";
|
|
407
419
|
|
|
408
420
|
// Reads format, dimensions, bit depth, and alpha channel straight from
|
|
409
|
-
// file bytes
|
|
421
|
+
// file bytes: no decoding, no canvas, no image library.
|
|
410
422
|
const bytes = await fs.promises.readFile("photo.png");
|
|
411
423
|
readImageInfo(new Uint8Array(bytes));
|
|
412
424
|
// { format: "png", width: 1920, height: 1080, bitDepth: 8, colorType: "rgba", hasAlpha: true }
|
|
@@ -424,24 +436,24 @@ Supports PNG, JPEG, GIF, WebP, and BMP.
|
|
|
424
436
|
| `@utilix-tech/sdk/encoding` | Base64, URL, HTML entities, Base32 encode/decode |
|
|
425
437
|
| `@utilix-tech/sdk/hashing` | MD5, SHA-1/256/512, bcrypt, HMAC, .htpasswd |
|
|
426
438
|
| `@utilix-tech/sdk/text` | Case convert, slugify, word count, lorem ipsum, line ops, HTML→Markdown |
|
|
427
|
-
| `@utilix-tech/sdk/data` | CSV, YAML, TOML, XML, INI parse and convert |
|
|
439
|
+
| `@utilix-tech/sdk/data` | CSV, YAML, TOML, XML, INI, NDJSON parse and convert |
|
|
428
440
|
| `@utilix-tech/sdk/generators` | UUID v4/v7, ULID, passwords, strength check, fake data |
|
|
429
441
|
| `@utilix-tech/sdk/time` | Date diff, timezone convert, cron parse, relative time |
|
|
430
442
|
| `@utilix-tech/sdk/units` | Bytes, CSS px/rem, number bases, currency format |
|
|
431
|
-
| `@utilix-tech/sdk/network` | HTTP status codes, IP validation, country flags, geolocation URLs |
|
|
443
|
+
| `@utilix-tech/sdk/network` | HTTP status codes, IP validation, country flags, geolocation URLs, HAR file parsing |
|
|
432
444
|
| `@utilix-tech/sdk/api` | cURL build/parse, JWT decode/sign, CORS headers, CSP header |
|
|
433
445
|
| `@utilix-tech/sdk/code` | SQL format/minify, HTML format/minify, regex tester, GraphQL format |
|
|
434
446
|
| `@utilix-tech/sdk/color` | Hex/RGB/HSL/HSV/CMYK convert, palette generation, contrast check |
|
|
435
447
|
| `@utilix-tech/sdk/css` | CSS gradient, box-shadow, border-radius, keyframe animation generators |
|
|
436
448
|
| `@utilix-tech/sdk/misc` | SVG optimize/sanitize, file size format, Unicode inspector |
|
|
437
449
|
| `@utilix-tech/sdk/ai_agent` | Token estimate/trim, chunk text, extract URLs/JSON/keywords, sanitize HTML, flatten/merge JSON, dedupe lines, validate schema, PII/secret/injection detect |
|
|
438
|
-
| `@utilix-tech/sdk/media` | Image format & dimension reading from raw bytes
|
|
450
|
+
| `@utilix-tech/sdk/media` | Image format & dimension reading from raw bytes: PNG, JPEG, GIF, WebP, BMP |
|
|
439
451
|
|
|
440
452
|
---
|
|
441
453
|
|
|
442
454
|
## Python SDK
|
|
443
455
|
|
|
444
|
-
`@utilix-tech/sdk` has a Python companion
|
|
456
|
+
`@utilix-tech/sdk` has a Python companion, [`utilix-sdk`](https://pypi.org/project/utilix-sdk/) on PyPI, with the same 140+ tools and identical return shapes.
|
|
445
457
|
|
|
446
458
|
```bash
|
|
447
459
|
pip install utilix-sdk
|
|
@@ -453,9 +465,9 @@ Both SDKs return plain JSON-serializable objects so you can share test fixtures
|
|
|
453
465
|
|
|
454
466
|
## REST API
|
|
455
467
|
|
|
456
|
-
**This package is Surface A
|
|
468
|
+
**This package is Surface A**: everything runs in-process in your Node.js runtime. No outbound requests, no API key, no rate limits.
|
|
457
469
|
|
|
458
|
-
The same
|
|
470
|
+
The same 140+ tools are also available as a hosted **REST API** at `https://api.utilix.tech/v1` for environments where you can't ship a Node.js runtime, or for cross-language teams.
|
|
459
471
|
|
|
460
472
|
```bash
|
|
461
473
|
curl -X POST https://api.utilix.tech/v1/tools/hash \
|
|
@@ -464,17 +476,17 @@ curl -X POST https://api.utilix.tech/v1/tools/hash \
|
|
|
464
476
|
-d '{"input": "hello world", "algorithm": "sha256"}'
|
|
465
477
|
```
|
|
466
478
|
|
|
467
|
-
- **Free**: 1,000 requests/day
|
|
468
|
-
- **Pro**: 10,000 requests/day
|
|
469
|
-
- Try it live at **[utilix.tech/api](https://utilix.tech/api)
|
|
479
|
+
- **Free**: 1,000 requests/day: no credit card required
|
|
480
|
+
- **Pro**: 10,000 requests/day: $9/month
|
|
481
|
+
- Try it live at **[utilix.tech/api](https://utilix.tech/api)**: no signup needed for the first 10 endpoints
|
|
470
482
|
- Get your API key at **[utilix.tech/dashboard](https://utilix.tech/dashboard)**
|
|
471
483
|
|
|
472
484
|
---
|
|
473
485
|
|
|
474
486
|
## Publishing (maintainers)
|
|
475
487
|
|
|
476
|
-
CI publishes automatically via npm Trusted Publishing (OIDC) on every push to `main` that bumps `version`. Requires npm **>=11.5.1
|
|
488
|
+
CI publishes automatically via npm Trusted Publishing (OIDC) on every push to `main` that bumps `version`. Requires npm **>=11.5.1**: older versions (e.g. the npm bundled with Node 20) sign provenance but skip the OIDC auth exchange, so `npm publish` fails with a 404 as if unauthenticated.
|
|
477
489
|
|
|
478
490
|
## License
|
|
479
491
|
|
|
480
|
-
MIT
|
|
492
|
+
MIT: see [LICENSE](./LICENSE) for details.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
If you discover a security vulnerability in this package, please report it privately rather than opening a public issue.
|
|
6
|
+
|
|
7
|
+
Email **hello@utilix.tech** with the subject line "Security report" and include:
|
|
8
|
+
- A description of the vulnerability and its potential impact
|
|
9
|
+
- Steps to reproduce, if possible
|
|
10
|
+
- Any suggested fix
|
|
11
|
+
|
|
12
|
+
We aim to acknowledge reports within 3 business days. Please give us a reasonable window to release a fix before disclosing publicly.
|
|
13
|
+
|
|
14
|
+
## Supported Versions
|
|
15
|
+
|
|
16
|
+
Only the latest published version of `@utilix-tech/sdk` receives security fixes. We recommend always running the latest release.
|
|
@@ -9,6 +9,7 @@ __export(data_exports, {
|
|
|
9
9
|
detectDelimiter: () => detectDelimiter,
|
|
10
10
|
envToExport: () => envToExport,
|
|
11
11
|
envToJson: () => envToJson,
|
|
12
|
+
formatNdjson: () => formatNdjson,
|
|
12
13
|
formatToml: () => formatToml,
|
|
13
14
|
formatXml: () => formatXml,
|
|
14
15
|
getSection: () => getSection,
|
|
@@ -17,11 +18,14 @@ __export(data_exports, {
|
|
|
17
18
|
getXmlStats: () => getXmlStats,
|
|
18
19
|
getYamlStats: () => getYamlStats,
|
|
19
20
|
iniToJson: () => iniToJson,
|
|
21
|
+
jsonArrayToNdjson: () => jsonArrayToNdjson,
|
|
20
22
|
jsonToIni: () => jsonToIni,
|
|
21
23
|
jsonToToml: () => jsonToToml,
|
|
22
24
|
jsonToXml: () => jsonToXml,
|
|
25
|
+
minifyNdjson: () => minifyNdjson,
|
|
23
26
|
minifyToml: () => minifyToml,
|
|
24
27
|
minifyXml: () => minifyXml,
|
|
28
|
+
ndjsonToJsonArray: () => ndjsonToJsonArray,
|
|
25
29
|
parseCsv: () => parseCsv,
|
|
26
30
|
parseEnv: () => parseEnv,
|
|
27
31
|
parseIni: () => parseIni,
|
|
@@ -31,6 +35,7 @@ __export(data_exports, {
|
|
|
31
35
|
tomlToJson: () => tomlToJson,
|
|
32
36
|
validateEnvKey: () => validateEnvKey,
|
|
33
37
|
validateIni: () => validateIni,
|
|
38
|
+
validateNdjson: () => validateNdjson,
|
|
34
39
|
validateToml: () => validateToml,
|
|
35
40
|
validateXml: () => validateXml,
|
|
36
41
|
validateYaml: () => validateYaml,
|
|
@@ -1076,5 +1081,63 @@ function envToExport(input) {
|
|
|
1076
1081
|
function validateEnvKey(key) {
|
|
1077
1082
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
|
|
1078
1083
|
}
|
|
1084
|
+
function validateNdjson(input) {
|
|
1085
|
+
const rawLines = input.split("\n");
|
|
1086
|
+
const lines = [];
|
|
1087
|
+
let validCount = 0;
|
|
1088
|
+
rawLines.forEach((raw, idx) => {
|
|
1089
|
+
if (raw.trim() === "") return;
|
|
1090
|
+
const lineNum = idx + 1;
|
|
1091
|
+
try {
|
|
1092
|
+
JSON.parse(raw);
|
|
1093
|
+
lines.push({ line: lineNum, raw, valid: true });
|
|
1094
|
+
validCount++;
|
|
1095
|
+
} catch (e) {
|
|
1096
|
+
lines.push({
|
|
1097
|
+
line: lineNum,
|
|
1098
|
+
raw,
|
|
1099
|
+
valid: false,
|
|
1100
|
+
error: e instanceof Error ? e.message : String(e)
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
return {
|
|
1105
|
+
valid: lines.length > 0 && validCount === lines.length,
|
|
1106
|
+
totalLines: lines.length,
|
|
1107
|
+
validLines: validCount,
|
|
1108
|
+
invalidLines: lines.length - validCount,
|
|
1109
|
+
lines
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
function _firstNdjsonError(result) {
|
|
1113
|
+
const firstError = result.lines.find((l) => !l.valid);
|
|
1114
|
+
return firstError ? `Line ${firstError.line}: ${firstError.error}` : "Invalid NDJSON";
|
|
1115
|
+
}
|
|
1116
|
+
function minifyNdjson(input) {
|
|
1117
|
+
const result = validateNdjson(input);
|
|
1118
|
+
if (!result.valid) return { error: _firstNdjsonError(result) };
|
|
1119
|
+
return result.lines.map((l) => JSON.stringify(JSON.parse(l.raw))).join("\n");
|
|
1120
|
+
}
|
|
1121
|
+
function formatNdjson(input, indent = 2) {
|
|
1122
|
+
const result = validateNdjson(input);
|
|
1123
|
+
if (!result.valid) return { error: _firstNdjsonError(result) };
|
|
1124
|
+
return result.lines.map((l) => JSON.stringify(JSON.parse(l.raw), null, indent)).join("\n");
|
|
1125
|
+
}
|
|
1126
|
+
function ndjsonToJsonArray(input) {
|
|
1127
|
+
const result = validateNdjson(input);
|
|
1128
|
+
if (!result.valid) return { error: _firstNdjsonError(result) };
|
|
1129
|
+
const values = result.lines.map((l) => JSON.parse(l.raw));
|
|
1130
|
+
return JSON.stringify(values, null, 2);
|
|
1131
|
+
}
|
|
1132
|
+
function jsonArrayToNdjson(input) {
|
|
1133
|
+
let parsed;
|
|
1134
|
+
try {
|
|
1135
|
+
parsed = JSON.parse(input);
|
|
1136
|
+
} catch (e) {
|
|
1137
|
+
return { error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
1138
|
+
}
|
|
1139
|
+
if (!Array.isArray(parsed)) return { error: "Input must be a JSON array" };
|
|
1140
|
+
return parsed.map((item) => JSON.stringify(item)).join("\n");
|
|
1141
|
+
}
|
|
1079
1142
|
|
|
1080
|
-
export { csvToJson, data_exports, detectDelimiter, envToExport, envToJson, formatToml, formatXml, getSection, getSectionNames, getTomlStats, getXmlStats, getYamlStats, iniToJson, jsonToIni, jsonToToml, jsonToXml, minifyToml, minifyXml, parseCsv, parseEnv, parseIni, parseXmlToObject, stringifyIni, toCsvString, tomlToJson, validateEnvKey, validateIni, validateToml, validateXml, validateYaml, xmlToJson };
|
|
1143
|
+
export { csvToJson, data_exports, detectDelimiter, envToExport, envToJson, formatNdjson, formatToml, formatXml, getSection, getSectionNames, getTomlStats, getXmlStats, getYamlStats, iniToJson, jsonArrayToNdjson, jsonToIni, jsonToToml, jsonToXml, minifyNdjson, minifyToml, minifyXml, ndjsonToJsonArray, parseCsv, parseEnv, parseIni, parseXmlToObject, stringifyIni, toCsvString, tomlToJson, validateEnvKey, validateIni, validateNdjson, validateToml, validateXml, validateYaml, xmlToJson };
|
|
@@ -570,7 +570,7 @@ function getConversionWarnings(from, to) {
|
|
|
570
570
|
warnings.push("Transparency will be lost if the WebP image has an alpha channel");
|
|
571
571
|
}
|
|
572
572
|
if (to === "image/jpeg") {
|
|
573
|
-
warnings.push("JPEG uses lossy compression
|
|
573
|
+
warnings.push("JPEG uses lossy compression: some quality may be reduced");
|
|
574
574
|
}
|
|
575
575
|
return warnings;
|
|
576
576
|
}
|
|
@@ -451,13 +451,13 @@ function decodeJwt(token) {
|
|
|
451
451
|
try {
|
|
452
452
|
header = decodeSegment(rawHeader);
|
|
453
453
|
} catch {
|
|
454
|
-
return { ok: false, error: "Failed to decode header
|
|
454
|
+
return { ok: false, error: "Failed to decode header: not valid base64url JSON" };
|
|
455
455
|
}
|
|
456
456
|
let payload;
|
|
457
457
|
try {
|
|
458
458
|
payload = decodeSegment(rawPayload);
|
|
459
459
|
} catch {
|
|
460
|
-
return { ok: false, error: "Failed to decode payload
|
|
460
|
+
return { ok: false, error: "Failed to decode payload: not valid base64url JSON" };
|
|
461
461
|
}
|
|
462
462
|
const algorithm = typeof header.alg === "string" ? header.alg : "unknown";
|
|
463
463
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -45,7 +45,7 @@ function decodeBase64(input, variant = "standard") {
|
|
|
45
45
|
const output = bytes.toString("utf8");
|
|
46
46
|
return { ok: true, output, outputBytes: bytes.length };
|
|
47
47
|
} catch {
|
|
48
|
-
return { ok: false, error: "Invalid Base64
|
|
48
|
+
return { ok: false, error: "Invalid Base64: check the input and variant setting" };
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
function encodeUrl(input, mode = "component") {
|
|
@@ -64,7 +64,7 @@ function decodeUrl(input, mode = "component") {
|
|
|
64
64
|
const output = mode === "component" ? decodeURIComponent(trimmed) : decodeURI(trimmed);
|
|
65
65
|
return { ok: true, output, changed: output !== trimmed };
|
|
66
66
|
} catch {
|
|
67
|
-
return { ok: false, error: "Invalid percent-encoding
|
|
67
|
+
return { ok: false, error: "Invalid percent-encoding: check the input" };
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
function diffSegments(original, encoded) {
|
|
@@ -128,7 +128,7 @@ function readImageInfo(bytes) {
|
|
|
128
128
|
if (bytes[0] === 255 && bytes[1] === 216) {
|
|
129
129
|
return readJpeg(bytes);
|
|
130
130
|
}
|
|
131
|
-
return { error: "Unrecognized image format
|
|
131
|
+
return { error: "Unrecognized image format. Supported formats: PNG, JPEG, GIF, WebP, BMP" };
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export { media_exports, readImageInfo };
|
|
@@ -82,16 +82,16 @@ function formatValue(value, unit, decimals = 4) {
|
|
|
82
82
|
return `${value.toFixed(decimals)}${unit}`;
|
|
83
83
|
}
|
|
84
84
|
var COMMON_SIZES = [
|
|
85
|
-
{ px: 8, label: "8px
|
|
86
|
-
{ px: 12, label: "12px
|
|
87
|
-
{ px: 14, label: "14px
|
|
88
|
-
{ px: 16, label: "16px
|
|
89
|
-
{ px: 18, label: "18px
|
|
90
|
-
{ px: 20, label: "20px
|
|
91
|
-
{ px: 24, label: "24px
|
|
92
|
-
{ px: 32, label: "32px
|
|
93
|
-
{ px: 48, label: "48px
|
|
94
|
-
{ px: 64, label: "64px
|
|
85
|
+
{ px: 8, label: "8px: Extra small spacing / icon size" },
|
|
86
|
+
{ px: 12, label: "12px: Small text / caption" },
|
|
87
|
+
{ px: 14, label: "14px: Body small / secondary text" },
|
|
88
|
+
{ px: 16, label: "16px: Base font size / body text" },
|
|
89
|
+
{ px: 18, label: "18px: Body large / lead text" },
|
|
90
|
+
{ px: 20, label: "20px: Subheading / large body" },
|
|
91
|
+
{ px: 24, label: "24px: H4 / section title" },
|
|
92
|
+
{ px: 32, label: "32px: H3 / card heading" },
|
|
93
|
+
{ px: 48, label: "48px: H2 / page section heading" },
|
|
94
|
+
{ px: 64, label: "64px: H1 / hero heading" }
|
|
95
95
|
];
|
|
96
96
|
function pxToViewport(px, viewportWidth = 1440, viewportHeight = 900) {
|
|
97
97
|
const vw = parseFloat((px / viewportWidth * 100).toFixed(4));
|
|
@@ -25,6 +25,7 @@ __export(network_exports, {
|
|
|
25
25
|
isValidIpv6: () => isValidIpv6,
|
|
26
26
|
parseDnsResponse: () => parseDnsResponse,
|
|
27
27
|
parseGeoResponse: () => parseGeoResponse,
|
|
28
|
+
parseHar: () => parseHar,
|
|
28
29
|
searchHeaders: () => searchHeaders,
|
|
29
30
|
searchStatusCodes: () => searchStatusCodes,
|
|
30
31
|
statusCodeToText: () => statusCodeToText,
|
|
@@ -1381,5 +1382,63 @@ function validateHeaderValue(name, value) {
|
|
|
1381
1382
|
}
|
|
1382
1383
|
return { valid: true };
|
|
1383
1384
|
}
|
|
1385
|
+
function parseHar(input) {
|
|
1386
|
+
if (!input || !input.trim()) {
|
|
1387
|
+
return { error: "Input is empty" };
|
|
1388
|
+
}
|
|
1389
|
+
let data;
|
|
1390
|
+
try {
|
|
1391
|
+
data = JSON.parse(input);
|
|
1392
|
+
} catch (e) {
|
|
1393
|
+
return { error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
1394
|
+
}
|
|
1395
|
+
if (typeof data !== "object" || data === null || !("log" in data)) {
|
|
1396
|
+
return { error: 'Not a valid HAR file: missing "log" property' };
|
|
1397
|
+
}
|
|
1398
|
+
const log = data.log;
|
|
1399
|
+
if (typeof log !== "object" || log === null || !Array.isArray(log.entries)) {
|
|
1400
|
+
return { error: 'Not a valid HAR file: "log.entries" must be an array' };
|
|
1401
|
+
}
|
|
1402
|
+
const logObj = log;
|
|
1403
|
+
const entries = [];
|
|
1404
|
+
const methods = {};
|
|
1405
|
+
const statusCodes = {};
|
|
1406
|
+
let totalSize = 0;
|
|
1407
|
+
let totalTime = 0;
|
|
1408
|
+
let failedRequests = 0;
|
|
1409
|
+
for (const raw of logObj.entries) {
|
|
1410
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
1411
|
+
const e = raw;
|
|
1412
|
+
const request = e.request ?? {};
|
|
1413
|
+
const response = e.response ?? {};
|
|
1414
|
+
const content = response.content ?? {};
|
|
1415
|
+
const method = typeof request.method === "string" ? request.method : "GET";
|
|
1416
|
+
const url = typeof request.url === "string" ? request.url : "";
|
|
1417
|
+
const status = typeof response.status === "number" ? response.status : 0;
|
|
1418
|
+
const statusText = typeof response.statusText === "string" ? response.statusText : "";
|
|
1419
|
+
const httpVersion = typeof response.httpVersion === "string" ? response.httpVersion : "";
|
|
1420
|
+
const mimeType = typeof content.mimeType === "string" ? content.mimeType : "";
|
|
1421
|
+
const size = typeof content.size === "number" && content.size > 0 ? content.size : 0;
|
|
1422
|
+
const time = typeof e.time === "number" ? e.time : 0;
|
|
1423
|
+
const startedDateTime = typeof e.startedDateTime === "string" ? e.startedDateTime : "";
|
|
1424
|
+
entries.push({ method, url, status, statusText, httpVersion, mimeType, size, time, startedDateTime });
|
|
1425
|
+
methods[method] = (methods[method] ?? 0) + 1;
|
|
1426
|
+
if (status > 0) statusCodes[String(status)] = (statusCodes[String(status)] ?? 0) + 1;
|
|
1427
|
+
totalSize += size;
|
|
1428
|
+
totalTime += time;
|
|
1429
|
+
if (status === 0 || status >= 400) failedRequests++;
|
|
1430
|
+
}
|
|
1431
|
+
const totalRequests = entries.length;
|
|
1432
|
+
const avgTime = totalRequests > 0 ? Math.round(totalTime / totalRequests * 100) / 100 : 0;
|
|
1433
|
+
return {
|
|
1434
|
+
version: typeof logObj.version === "string" ? logObj.version : "",
|
|
1435
|
+
creator: {
|
|
1436
|
+
name: typeof logObj.creator?.name === "string" ? logObj.creator.name : "",
|
|
1437
|
+
version: typeof logObj.creator?.version === "string" ? logObj.creator.version : ""
|
|
1438
|
+
},
|
|
1439
|
+
entries,
|
|
1440
|
+
summary: { totalRequests, totalSize, totalTime, avgTime, failedRequests, methods, statusCodes }
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1384
1443
|
|
|
1385
|
-
export { DNS_RECORD_TYPES, HTTP_HEADERS, STATUS_CODES, buildDnsQueryUrl, buildGeoUrl, buildMapUrl, countryCodeToFlag, formatCoordinates, formatTtl, getByCategory, getHeader, getHeadersByCategory, getStatusByCategory, getStatusCode, isClientError, isServerError, isSuccess, isValidIp, isValidIpv4, isValidIpv6, network_exports, parseDnsResponse, parseGeoResponse, searchHeaders, searchStatusCodes, statusCodeToText, typeNumberToLabel, validateDomain, validateHeaderName, validateHeaderValue };
|
|
1444
|
+
export { DNS_RECORD_TYPES, HTTP_HEADERS, STATUS_CODES, buildDnsQueryUrl, buildGeoUrl, buildMapUrl, countryCodeToFlag, formatCoordinates, formatTtl, getByCategory, getHeader, getHeadersByCategory, getStatusByCategory, getStatusCode, isClientError, isServerError, isSuccess, isValidIp, isValidIpv4, isValidIpv6, network_exports, parseDnsResponse, parseGeoResponse, parseHar, searchHeaders, searchStatusCodes, statusCodeToText, typeNumberToLabel, validateDomain, validateHeaderName, validateHeaderValue };
|
|
@@ -134,24 +134,24 @@ var CHEATSHEET = [
|
|
|
134
134
|
{ pattern: "{n}", description: "Matches exactly n repetitions", example: "\\d{3} matches exactly 3 digits", category: "Quantifiers" },
|
|
135
135
|
{ pattern: "{n,}", description: "Matches n or more repetitions", example: "\\d{2,} matches 2 or more digits", category: "Quantifiers" },
|
|
136
136
|
{ pattern: "{n,m}", description: "Matches between n and m repetitions", example: "\\d{2,4} matches 2 to 4 digits", category: "Quantifiers" },
|
|
137
|
-
{ pattern: "*?", description: "Lazy match
|
|
137
|
+
{ pattern: "*?", description: "Lazy match: matches as few characters as possible", example: "<.*?> matches the shortest possible HTML tag", category: "Quantifiers" },
|
|
138
138
|
{ pattern: "+?", description: "Lazy version of +", example: "a+? matches as few 'a's as possible", category: "Quantifiers" },
|
|
139
|
-
{ pattern: "(abc)", description: "Capturing group
|
|
140
|
-
{ pattern: "(?:abc)", description: "Non-capturing group
|
|
139
|
+
{ pattern: "(abc)", description: "Capturing group: captures the matched substring", example: "(\\d+) captures digit sequences", category: "Groups" },
|
|
140
|
+
{ pattern: "(?:abc)", description: "Non-capturing group: groups without capturing", example: "(?:foo|bar)baz matches 'foobaz' or 'barbaz'", category: "Groups" },
|
|
141
141
|
{ pattern: "(?<name>abc)", description: "Named capturing group", example: "(?<year>\\d{4}) captures year by name", category: "Groups" },
|
|
142
|
-
{ pattern: "a|b", description: "Alternation
|
|
142
|
+
{ pattern: "a|b", description: "Alternation: matches either a or b", example: "cat|dog matches 'cat' or 'dog'", category: "Groups" },
|
|
143
143
|
{ pattern: "\\1", description: "Backreference to the first capturing group", example: "(\\w+) \\1 matches repeated words like 'the the'", category: "Groups" },
|
|
144
144
|
{ pattern: "\\k<name>", description: "Backreference to a named capturing group", example: "(?<word>\\w+) \\k<word> matches repeated named group", category: "Groups" },
|
|
145
|
-
{ pattern: "(?=abc)", description: "Positive lookahead
|
|
146
|
-
{ pattern: "(?!abc)", description: "Negative lookahead
|
|
147
|
-
{ pattern: "(?<=abc)", description: "Positive lookbehind
|
|
148
|
-
{ pattern: "(?<!abc)", description: "Negative lookbehind
|
|
149
|
-
{ pattern: "g", description: "Global flag
|
|
150
|
-
{ pattern: "i", description: "Case-insensitive flag
|
|
151
|
-
{ pattern: "m", description: "Multiline flag
|
|
152
|
-
{ pattern: "s", description: "Dotall flag
|
|
153
|
-
{ pattern: "u", description: "Unicode flag
|
|
154
|
-
{ pattern: "y", description: "Sticky flag
|
|
145
|
+
{ pattern: "(?=abc)", description: "Positive lookahead: asserts what follows matches", example: "\\d+(?= dollars) matches digits followed by ' dollars'", category: "Lookahead/Lookbehind" },
|
|
146
|
+
{ pattern: "(?!abc)", description: "Negative lookahead: asserts what follows does not match", example: "\\d+(?! dollars) matches digits not followed by ' dollars'", category: "Lookahead/Lookbehind" },
|
|
147
|
+
{ pattern: "(?<=abc)", description: "Positive lookbehind: asserts what precedes matches", example: "(?<=\\$)\\d+ matches digits preceded by '$'", category: "Lookahead/Lookbehind" },
|
|
148
|
+
{ pattern: "(?<!abc)", description: "Negative lookbehind: asserts what precedes does not match", example: "(?<!\\$)\\d+ matches digits not preceded by '$'", category: "Lookahead/Lookbehind" },
|
|
149
|
+
{ pattern: "g", description: "Global flag: finds all matches rather than stopping at the first", example: "/\\d+/g finds all numbers in a string", category: "Flags" },
|
|
150
|
+
{ pattern: "i", description: "Case-insensitive flag: ignores letter case", example: "/hello/i matches 'Hello', 'HELLO', 'hello'", category: "Flags" },
|
|
151
|
+
{ pattern: "m", description: "Multiline flag: ^ and $ match start/end of each line", example: "/^\\w+/m matches the first word of each line", category: "Flags" },
|
|
152
|
+
{ pattern: "s", description: "Dotall flag: makes . match newline characters too", example: "/a.b/s matches 'a\\nb'", category: "Flags" },
|
|
153
|
+
{ pattern: "u", description: "Unicode flag: enables full Unicode matching", example: "/\\u{1F600}/u matches a Unicode emoji", category: "Flags" },
|
|
154
|
+
{ pattern: "y", description: "Sticky flag: matches only from the lastIndex position", example: "/\\d+/y matches digits starting at the current position", category: "Flags" },
|
|
155
155
|
{ pattern: "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", description: "Matches a basic email address", example: "user@example.com", category: "Common Patterns" },
|
|
156
156
|
{ pattern: "https?://[\\w\\-]+(\\.[\\w\\-]+)+([\\w.,@?^=%&:/~+#\\-]*[\\w@?^=%&/~+#\\-])?", description: "Matches an HTTP or HTTPS URL", example: "https://www.example.com/path?query=value", category: "Common Patterns" },
|
|
157
157
|
{ pattern: "^(?:\\+1)?[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$", description: "Matches a North American phone number in various formats", example: "(555) 123-4567 or 555-123-4567", category: "Common Patterns" },
|