@utilix-tech/sdk 0.2.0 → 0.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/CHANGELOG.md +15 -0
- package/README.md +62 -25
- package/SECURITY.md +16 -0
- 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-N7C52YGL.js +134 -0
- package/dist/{chunk-XST6X3HT.js → chunk-QJE743LY.js} +10 -10
- package/dist/{chunk-NSOARQCM.js → chunk-V5S6OJ4A.js} +14 -14
- package/dist/{chunk-XXYZLLHI.js → chunk-YBBYFAOV.js} +167 -1
- package/dist/index.cjs +327 -29
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -6
- package/dist/media-DF2cx3pK.d.cts +28 -0
- package/dist/media-DF2cx3pK.d.ts +28 -0
- package/dist/{text-DqAjPtQ0.d.cts → text-CI7JAl7F.d.cts} +28 -2
- package/dist/{text-DqAjPtQ0.d.ts → text-CI7JAl7F.d.ts} +28 -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/encoding.cjs +2 -2
- package/dist/tools/encoding.js +1 -1
- package/dist/tools/media.cjs +130 -0
- package/dist/tools/media.d.cts +1 -0
- package/dist/tools/media.d.ts +1 -0
- package/dist/tools/media.js +2 -0
- package/dist/tools/misc.cjs +1 -1
- package/dist/tools/misc.js +1 -1
- package/dist/tools/text.cjs +166 -0
- package/dist/tools/text.d.cts +1 -1
- package/dist/tools/text.d.ts +1 -1
- package/dist/tools/text.js +1 -1
- package/dist/tools/units.cjs +10 -10
- package/dist/tools/units.js +1 -1
- package/package.json +23 -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)
|
|
@@ -26,13 +26,13 @@ yarn add @utilix-tech/sdk
|
|
|
26
26
|
pnpm add @utilix-tech/sdk
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
**Requires Node.js 18 or later.**
|
|
29
|
+
**Requires Node.js 18 or later.** The public npm package includes bundled TypeScript types, so no `@types/` package is needed.
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
33
|
## Quick Start
|
|
34
34
|
|
|
35
|
-
Each of the
|
|
35
|
+
Each of the 16 modules is available as a dedicated subpath import. Import only what you use; bundlers automatically tree-shake the rest.
|
|
36
36
|
|
|
37
37
|
```ts
|
|
38
38
|
// Pick exactly the modules you need
|
|
@@ -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,11 +123,11 @@ 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,
|
|
130
|
-
escapeString, htmlToMarkdown, applyOps } from "@utilix-tech/sdk/text";
|
|
130
|
+
escapeString, htmlToMarkdown, applyOps, detectPassiveVoice } from "@utilix-tech/sdk/text";
|
|
131
131
|
|
|
132
132
|
convertCase("hello world", "camelCase"); // "helloWorld"
|
|
133
133
|
convertCase("hello world", "PascalCase"); // "HelloWorld"
|
|
@@ -145,11 +145,14 @@ generateParagraphs(3); // lorem ipsum paragraphs
|
|
|
145
145
|
applyOps(text, ["sort", "deduplicate", "trim"]);
|
|
146
146
|
|
|
147
147
|
htmlToMarkdown("<h1>Hello</h1><p>World</p>");
|
|
148
|
+
|
|
149
|
+
// Flag passive-voice sentences, e.g. "was written", "were approved by the team"
|
|
150
|
+
detectPassiveVoice("The report was reviewed by the committee. They approved it.");
|
|
148
151
|
```
|
|
149
152
|
|
|
150
153
|
---
|
|
151
154
|
|
|
152
|
-
### `/data
|
|
155
|
+
### `/data`: CSV / YAML / TOML / XML / INI
|
|
153
156
|
|
|
154
157
|
```ts
|
|
155
158
|
import { parseCsv, csvToJson, validateYaml, tomlToJson, jsonToToml,
|
|
@@ -176,7 +179,7 @@ parseIni("[db]\nhost=localhost\nport=5432");
|
|
|
176
179
|
|
|
177
180
|
---
|
|
178
181
|
|
|
179
|
-
### `/generators
|
|
182
|
+
### `/generators`: UUID, Passwords, Fake Data
|
|
180
183
|
|
|
181
184
|
```ts
|
|
182
185
|
import { generateUuid, generateV4, generateV7, generateUlid,
|
|
@@ -199,7 +202,7 @@ generateData([
|
|
|
199
202
|
|
|
200
203
|
---
|
|
201
204
|
|
|
202
|
-
### `/time
|
|
205
|
+
### `/time`: Dates, Timezones, Cron
|
|
203
206
|
|
|
204
207
|
```ts
|
|
205
208
|
import { diffDates, parseCron, convertTime, formatRelative,
|
|
@@ -222,7 +225,7 @@ getNextRuns("*/5 * * * *", 5); // next 5 run timestamps
|
|
|
222
225
|
|
|
223
226
|
---
|
|
224
227
|
|
|
225
|
-
### `/units
|
|
228
|
+
### `/units`: Conversions
|
|
226
229
|
|
|
227
230
|
```ts
|
|
228
231
|
import { convertBytes, pxToAll, convert, formatValue } from "@utilix-tech/sdk/units";
|
|
@@ -247,7 +250,7 @@ formatValue(1234567.89, { locale: "en-US", currency: "USD" });
|
|
|
247
250
|
|
|
248
251
|
---
|
|
249
252
|
|
|
250
|
-
### `/network
|
|
253
|
+
### `/network`: URLs, IPs, HTTP
|
|
251
254
|
|
|
252
255
|
```ts
|
|
253
256
|
import { getStatusCode, isValidIp, isValidIpv4, isValidIpv6,
|
|
@@ -267,7 +270,7 @@ countryCodeToFlag("US"); // "🇺🇸"
|
|
|
267
270
|
|
|
268
271
|
---
|
|
269
272
|
|
|
270
|
-
### `/api
|
|
273
|
+
### `/api`: cURL, JWT, CORS, HTTP
|
|
271
274
|
|
|
272
275
|
```ts
|
|
273
276
|
import { buildCurl, parseCurlCommand, decodeJwt, signJwt,
|
|
@@ -286,7 +289,7 @@ buildCurl({
|
|
|
286
289
|
// Parse a curl command back into its parts
|
|
287
290
|
parseCurlCommand('curl -X GET https://api.example.com -H "Authorization: Bearer token"');
|
|
288
291
|
|
|
289
|
-
// JWT
|
|
292
|
+
// JWT: decode without verification
|
|
290
293
|
decodeJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...");
|
|
291
294
|
// { header: {...}, payload: {...}, isExpired: false, expiresIn: "2h" }
|
|
292
295
|
|
|
@@ -299,7 +302,7 @@ generateCspHeader({ "default-src": ["'self'"], "script-src": ["'self'", "cdn.exa
|
|
|
299
302
|
|
|
300
303
|
---
|
|
301
304
|
|
|
302
|
-
### `/code
|
|
305
|
+
### `/code`: SQL, HTML, RegEx, GQL
|
|
303
306
|
|
|
304
307
|
```ts
|
|
305
308
|
import { formatSql, minifySql, testRegex, formatHtml, formatGql,
|
|
@@ -331,7 +334,7 @@ formatGql("query { user(id: 1) { name email } }");
|
|
|
331
334
|
|
|
332
335
|
---
|
|
333
336
|
|
|
334
|
-
### `/color
|
|
337
|
+
### `/color`: Color Conversion & Palettes
|
|
335
338
|
|
|
336
339
|
```ts
|
|
337
340
|
import { parseColor, convertColor, generatePalette, checkContrast } from "@utilix-tech/sdk/color";
|
|
@@ -351,7 +354,7 @@ checkContrast("#ffffff", "#000000");
|
|
|
351
354
|
|
|
352
355
|
---
|
|
353
356
|
|
|
354
|
-
### `/css
|
|
357
|
+
### `/css`: CSS Generators
|
|
355
358
|
|
|
356
359
|
```ts
|
|
357
360
|
import { generateGradient, generateBoxShadow, generateBorderRadius,
|
|
@@ -373,7 +376,7 @@ generateBorderRadius({ tl: 8, tr: 8, br: 0, bl: 0 });
|
|
|
373
376
|
|
|
374
377
|
---
|
|
375
378
|
|
|
376
|
-
### `/misc
|
|
379
|
+
### `/misc`: SVG, QR, Unicode
|
|
377
380
|
|
|
378
381
|
```ts
|
|
379
382
|
import { optimizeSvg, sanitizeSvg, analyzeString, toUnicodeEscape,
|
|
@@ -397,6 +400,22 @@ analyzeString("café"); // { length: 4, codePoints: [...], ... }
|
|
|
397
400
|
|
|
398
401
|
---
|
|
399
402
|
|
|
403
|
+
### `/media`: Image Header Parsing
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import { readImageInfo } from "@utilix-tech/sdk/media";
|
|
407
|
+
|
|
408
|
+
// Reads format, dimensions, bit depth, and alpha channel straight from
|
|
409
|
+
// file bytes: no decoding, no canvas, no image library.
|
|
410
|
+
const bytes = await fs.promises.readFile("photo.png");
|
|
411
|
+
readImageInfo(new Uint8Array(bytes));
|
|
412
|
+
// { format: "png", width: 1920, height: 1080, bitDepth: 8, colorType: "rgba", hasAlpha: true }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Supports PNG, JPEG, GIF, WebP, and BMP.
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
400
419
|
## All Modules at a Glance
|
|
401
420
|
|
|
402
421
|
| Import | What it does |
|
|
@@ -415,12 +434,14 @@ analyzeString("café"); // { length: 4, codePoints: [...], ... }
|
|
|
415
434
|
| `@utilix-tech/sdk/color` | Hex/RGB/HSL/HSV/CMYK convert, palette generation, contrast check |
|
|
416
435
|
| `@utilix-tech/sdk/css` | CSS gradient, box-shadow, border-radius, keyframe animation generators |
|
|
417
436
|
| `@utilix-tech/sdk/misc` | SVG optimize/sanitize, file size format, Unicode inspector |
|
|
437
|
+
| `@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: PNG, JPEG, GIF, WebP, BMP |
|
|
418
439
|
|
|
419
440
|
---
|
|
420
441
|
|
|
421
442
|
## Python SDK
|
|
422
443
|
|
|
423
|
-
`@utilix-tech/sdk` has a Python companion
|
|
444
|
+
`@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.
|
|
424
445
|
|
|
425
446
|
```bash
|
|
426
447
|
pip install utilix-sdk
|
|
@@ -430,14 +451,30 @@ Both SDKs return plain JSON-serializable objects so you can share test fixtures
|
|
|
430
451
|
|
|
431
452
|
---
|
|
432
453
|
|
|
433
|
-
##
|
|
454
|
+
## REST API
|
|
455
|
+
|
|
456
|
+
**This package is Surface A**: everything runs in-process in your Node.js runtime. No outbound requests, no API key, no rate limits.
|
|
434
457
|
|
|
435
|
-
|
|
458
|
+
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.
|
|
436
459
|
|
|
437
|
-
|
|
460
|
+
```bash
|
|
461
|
+
curl -X POST https://api.utilix.tech/v1/tools/hash \
|
|
462
|
+
-H "Authorization: Bearer utx_live_..." \
|
|
463
|
+
-H "Content-Type: application/json" \
|
|
464
|
+
-d '{"input": "hello world", "algorithm": "sha256"}'
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
- **Free**: 1,000 requests/day: no credit card required
|
|
468
|
+
- **Pro**: 10,000 requests/day: $9/month
|
|
469
|
+
- Try it live at **[utilix.tech/api](https://utilix.tech/api)**: no signup needed for the first 10 endpoints
|
|
470
|
+
- Get your API key at **[utilix.tech/dashboard](https://utilix.tech/dashboard)**
|
|
438
471
|
|
|
439
472
|
---
|
|
440
473
|
|
|
474
|
+
## Publishing (maintainers)
|
|
475
|
+
|
|
476
|
+
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
|
+
|
|
441
478
|
## License
|
|
442
479
|
|
|
443
|
-
MIT
|
|
480
|
+
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.
|
|
@@ -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) {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { __export } from './chunk-MLKGABMK.js';
|
|
2
|
+
|
|
3
|
+
// src/tools/media.ts
|
|
4
|
+
var media_exports = {};
|
|
5
|
+
__export(media_exports, {
|
|
6
|
+
readImageInfo: () => readImageInfo
|
|
7
|
+
});
|
|
8
|
+
function readU32BE(bytes, offset) {
|
|
9
|
+
return (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
|
|
10
|
+
}
|
|
11
|
+
function readI32LE(bytes, offset) {
|
|
12
|
+
return bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16 | bytes[offset + 3] << 24;
|
|
13
|
+
}
|
|
14
|
+
var PNG_COLOR_TYPES = {
|
|
15
|
+
0: "grayscale",
|
|
16
|
+
2: "rgb",
|
|
17
|
+
3: "palette",
|
|
18
|
+
4: "grayscale+alpha",
|
|
19
|
+
6: "rgba"
|
|
20
|
+
};
|
|
21
|
+
function readPng(bytes) {
|
|
22
|
+
if (bytes.length < 33) return { error: "Truncated PNG file" };
|
|
23
|
+
const width = readU32BE(bytes, 16);
|
|
24
|
+
const height = readU32BE(bytes, 20);
|
|
25
|
+
const bitDepth = bytes[24];
|
|
26
|
+
const colorTypeNum = bytes[25];
|
|
27
|
+
const colorType = PNG_COLOR_TYPES[colorTypeNum] ?? "unknown";
|
|
28
|
+
const hasAlpha = colorTypeNum === 4 || colorTypeNum === 6;
|
|
29
|
+
return { format: "png", width, height, bitDepth, colorType, hasAlpha };
|
|
30
|
+
}
|
|
31
|
+
function readGif(bytes) {
|
|
32
|
+
if (bytes.length < 10) return { error: "Truncated GIF file" };
|
|
33
|
+
const width = bytes[6] | bytes[7] << 8;
|
|
34
|
+
const height = bytes[8] | bytes[9] << 8;
|
|
35
|
+
return { format: "gif", width, height };
|
|
36
|
+
}
|
|
37
|
+
function readBmp(bytes) {
|
|
38
|
+
if (bytes.length < 30) return { error: "Truncated BMP file" };
|
|
39
|
+
const width = readI32LE(bytes, 18);
|
|
40
|
+
const heightRaw = readI32LE(bytes, 22);
|
|
41
|
+
const bitDepth = bytes[28] | bytes[29] << 8;
|
|
42
|
+
return { format: "bmp", width: Math.abs(width), height: Math.abs(heightRaw), bitDepth };
|
|
43
|
+
}
|
|
44
|
+
function readWebp(bytes) {
|
|
45
|
+
if (bytes.length < 16) return { error: "Truncated WebP file" };
|
|
46
|
+
const fourCC = String.fromCharCode(bytes[12], bytes[13], bytes[14], bytes[15]);
|
|
47
|
+
if (fourCC === "VP8 ") {
|
|
48
|
+
if (bytes.length < 30) return { error: "Truncated WebP file" };
|
|
49
|
+
const width = (bytes[26] | bytes[27] << 8) & 16383;
|
|
50
|
+
const height = (bytes[28] | bytes[29] << 8) & 16383;
|
|
51
|
+
return { format: "webp", width, height };
|
|
52
|
+
}
|
|
53
|
+
if (fourCC === "VP8L") {
|
|
54
|
+
if (bytes.length < 25) return { error: "Truncated WebP file" };
|
|
55
|
+
const bits = bytes[21] | bytes[22] << 8 | bytes[23] << 16 | bytes[24] << 24;
|
|
56
|
+
const width = (bits & 16383) + 1;
|
|
57
|
+
const height = (bits >>> 14 & 16383) + 1;
|
|
58
|
+
const hasAlpha = !!(bits >>> 28 & 1);
|
|
59
|
+
return { format: "webp", width, height, hasAlpha };
|
|
60
|
+
}
|
|
61
|
+
if (fourCC === "VP8X") {
|
|
62
|
+
if (bytes.length < 30) return { error: "Truncated WebP file" };
|
|
63
|
+
const flags = bytes[20];
|
|
64
|
+
const hasAlpha = !!(flags & 16);
|
|
65
|
+
const width = (bytes[24] | bytes[25] << 8 | bytes[26] << 16) + 1;
|
|
66
|
+
const height = (bytes[27] | bytes[28] << 8 | bytes[29] << 16) + 1;
|
|
67
|
+
return { format: "webp", width, height, hasAlpha };
|
|
68
|
+
}
|
|
69
|
+
return { error: "Unrecognized WebP chunk format" };
|
|
70
|
+
}
|
|
71
|
+
var JPEG_SOF_MARKERS = /* @__PURE__ */ new Set([
|
|
72
|
+
192,
|
|
73
|
+
193,
|
|
74
|
+
194,
|
|
75
|
+
195,
|
|
76
|
+
197,
|
|
77
|
+
198,
|
|
78
|
+
199,
|
|
79
|
+
201,
|
|
80
|
+
202,
|
|
81
|
+
203,
|
|
82
|
+
205,
|
|
83
|
+
206,
|
|
84
|
+
207
|
|
85
|
+
]);
|
|
86
|
+
function readJpeg(bytes) {
|
|
87
|
+
let offset = 2;
|
|
88
|
+
while (offset < bytes.length - 1) {
|
|
89
|
+
if (bytes[offset] !== 255) {
|
|
90
|
+
offset++;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const marker = bytes[offset + 1];
|
|
94
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
|
|
95
|
+
offset += 2;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (marker === 217) break;
|
|
99
|
+
if (offset + 3 >= bytes.length) break;
|
|
100
|
+
const segmentLength = bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
101
|
+
if (JPEG_SOF_MARKERS.has(marker)) {
|
|
102
|
+
if (offset + 9 >= bytes.length) return { error: "Truncated JPEG SOF segment" };
|
|
103
|
+
const bitDepth = bytes[offset + 4];
|
|
104
|
+
const height = bytes[offset + 5] << 8 | bytes[offset + 6];
|
|
105
|
+
const width = bytes[offset + 7] << 8 | bytes[offset + 8];
|
|
106
|
+
const components = bytes[offset + 9];
|
|
107
|
+
const colorType = components === 1 ? "grayscale" : components === 3 ? "rgb" : components === 4 ? "cmyk" : "unknown";
|
|
108
|
+
return { format: "jpeg", width, height, bitDepth, colorType };
|
|
109
|
+
}
|
|
110
|
+
offset += 2 + segmentLength;
|
|
111
|
+
}
|
|
112
|
+
return { error: "No SOF marker found in JPEG (file may be corrupt)" };
|
|
113
|
+
}
|
|
114
|
+
function readImageInfo(bytes) {
|
|
115
|
+
if (bytes.length < 10) return { error: "File too small to determine format" };
|
|
116
|
+
if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) {
|
|
117
|
+
return readPng(bytes);
|
|
118
|
+
}
|
|
119
|
+
if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) {
|
|
120
|
+
return readGif(bytes);
|
|
121
|
+
}
|
|
122
|
+
if (bytes[0] === 66 && bytes[1] === 77) {
|
|
123
|
+
return readBmp(bytes);
|
|
124
|
+
}
|
|
125
|
+
if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
|
|
126
|
+
return readWebp(bytes);
|
|
127
|
+
}
|
|
128
|
+
if (bytes[0] === 255 && bytes[1] === 216) {
|
|
129
|
+
return readJpeg(bytes);
|
|
130
|
+
}
|
|
131
|
+
return { error: "Unrecognized image format. Supported formats: PNG, JPEG, GIF, WebP, BMP" };
|
|
132
|
+
}
|
|
133
|
+
|
|
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));
|
|
@@ -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" },
|