@bleedingdev/ipx 3.2.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/LICENSE +21 -0
- package/README.md +115 -0
- package/bin/ipx.mjs +2 -0
- package/dist/cli.cjs +63 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.mjs +61 -0
- package/dist/index.cjs +62 -0
- package/dist/index.d.cts +397 -0
- package/dist/index.d.mts +397 -0
- package/dist/index.d.ts +397 -0
- package/dist/index.mjs +52 -0
- package/dist/shared/ipx.CXJeaylD.mjs +784 -0
- package/dist/shared/ipx.GUc23orS.cjs +798 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) Pooya Parsa <pooya@pi0.io>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @bleedingdev/ipx
|
|
2
|
+
|
|
3
|
+
A minimal, distributable sidecar fork of [`ipx`](https://github.com/unjs/ipx) — "High
|
|
4
|
+
performance, secure and easy-to-use image optimizer" — by Pooya Parsa and the UnJS
|
|
5
|
+
contributors, MIT licensed. The upstream `LICENSE` is preserved verbatim next to this file.
|
|
6
|
+
|
|
7
|
+
Forked from **`ipx@3.1.1`**. Published as **`@bleedingdev/ipx@3.2.0`**.
|
|
8
|
+
|
|
9
|
+
The `dist/` tree here is the upstream published build, vendored byte-for-byte except for the
|
|
10
|
+
hunks listed below. Upstream ships no TypeScript sources in the npm tarball, so this fork
|
|
11
|
+
patches the published chunks directly rather than rebuilding from source; that keeps the diff
|
|
12
|
+
against `ipx@3.1.1` small enough to re-audit by eye on every upstream bump.
|
|
13
|
+
|
|
14
|
+
## Why this fork exists
|
|
15
|
+
|
|
16
|
+
`@modern-js/image` depends on `sharp@^0.35.3`, but `ipx@3.1.1` declares `"sharp": "^0.34.3"`
|
|
17
|
+
as a **plain dependency**. A `0.x` caret does not admit `0.35.x`
|
|
18
|
+
(`semver.satisfies("0.35.3", "^0.34.3") === false`), so every consumer of the published
|
|
19
|
+
`@modern-js/image` also installs a second, older `sharp` — currently `sharp@0.34.5` — behind
|
|
20
|
+
IPX. Plain dependencies cannot be redirected from a consumer's manifest: `pnpm.overrides` and
|
|
21
|
+
`pnpm.patchedDependencies` apply only to the root project and are not carried into a published
|
|
22
|
+
package. Forking the dependency and aliasing it in `@modern-js/image` is the only distributable
|
|
23
|
+
fix.
|
|
24
|
+
|
|
25
|
+
The version is a **stable** `3.2.0` on purpose. Both `@rsbuild-image/core` and
|
|
26
|
+
`@rsbuild-image/react` declare the peer range `"ipx": ">=3.0.3"`, and npm evaluates non-wildcard
|
|
27
|
+
peer ranges with a loose-only `semver.satisfies` that **excludes prereleases**. A prerelease
|
|
28
|
+
version such as `3.2.0-ultramodern.1` would pass pnpm but fail strict npm and yarn-classic
|
|
29
|
+
consumers.
|
|
30
|
+
|
|
31
|
+
## Fork delta vs `ipx@3.1.1`
|
|
32
|
+
|
|
33
|
+
### 1. `sharp` dependency range: `^0.34.3` → `^0.35.3` (`package.json`)
|
|
34
|
+
|
|
35
|
+
Aligns IPX with the `sharp` version `@modern-js/image` already declares, so a single, current
|
|
36
|
+
`sharp` is installed for the whole image pipeline.
|
|
37
|
+
|
|
38
|
+
### 2. `sharpen` modifier remapped to Sharp 0.35's object form (`dist/shared/*`, 2 hunks)
|
|
39
|
+
|
|
40
|
+
Sharp 0.35 **removed** the deprecated positional `sharpen(sigma, flat, jagged)` signature. Its
|
|
41
|
+
replacement (`sharp/dist/operation.cjs`, `function sharpen (options)`) reads a plain object and
|
|
42
|
+
falls through to `this.options.sharpenSigma = -1` — the "mild sharpen" default — for **any**
|
|
43
|
+
non-object argument. Upstream's call therefore does not throw under Sharp 0.35; it *silently
|
|
44
|
+
discards every parameter the user asked for*.
|
|
45
|
+
|
|
46
|
+
Upstream (`dist/shared/ipx.CXJeaylD.mjs:213-217` and `dist/shared/ipx.GUc23orS.cjs:220-224`):
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
apply: (_context, pipe, sigma, flat, jagged) => {
|
|
50
|
+
return pipe.sharpen(sigma, flat, jagged);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This fork maps the IPX modifier arguments onto `{ sigma, m1, m2 }`, coercing each to a number
|
|
55
|
+
(IPX arguments arrive through `VArg`, i.e. `destr`, so a URL segment may still be a string) and
|
|
56
|
+
treating `undefined`, `null`, an empty segment, and a non-numeric segment as "absent" — which
|
|
57
|
+
yields Sharp's argument-less mild sharpen instead of a thrown parameter error. `NaN` is never
|
|
58
|
+
handed to Sharp.
|
|
59
|
+
|
|
60
|
+
Behaviour notes:
|
|
61
|
+
|
|
62
|
+
- `?sharpen` (bare, no value) now applies Sharp's mild sharpen. Under `ipx@3.1.1` + `sharp@0.34`
|
|
63
|
+
this threw `Expected number between 0.01 and 10000 for sigma`; the new behaviour matches
|
|
64
|
+
Sharp's own no-argument contract and IPX's convention that a bare modifier is a flag.
|
|
65
|
+
- The accepted `sigma` range narrows from Sharp 0.34's deprecated positional range
|
|
66
|
+
(`0.01`–`10000`) to Sharp 0.35's object-form range (`0.000001`–`10`). This is an upstream Sharp
|
|
67
|
+
restriction, not a fork decision; out-of-range values raise Sharp's own parameter error rather
|
|
68
|
+
than being silently clamped.
|
|
69
|
+
- `flat`/`jagged` map to `m1`/`m2`, whose valid range widens to `0`–`1000000` (Sharp 0.34's
|
|
70
|
+
deprecated path capped them at `10000`).
|
|
71
|
+
|
|
72
|
+
### 3. CLI banner version string `3.1.1` → `3.2.0` (`dist/cli.mjs`, `dist/cli.cjs`)
|
|
73
|
+
|
|
74
|
+
Cosmetic and non-functional: the version is inlined into the published CLI at build time, so
|
|
75
|
+
without this hunk `ipx --version` would disagree with this package's manifest. The bin name
|
|
76
|
+
itself is unchanged (`"bin": "./bin/ipx.mjs"` in string form, which
|
|
77
|
+
`npm-normalize-package-bin` resolves to the basename `ipx`), so the `ipx` CLI contract is
|
|
78
|
+
preserved despite the scoped package name.
|
|
79
|
+
|
|
80
|
+
## Sharp 0.34 → 0.35 audit (no other remaps required)
|
|
81
|
+
|
|
82
|
+
Every Sharp method IPX's modifier table reaches was compared between `sharp@0.34.5`
|
|
83
|
+
(`lib/*.js`) and `sharp@0.35.3` (`dist/*.cjs`): `resize`, `trim`, `extend`, `extract`, `rotate`,
|
|
84
|
+
`flip`, `flop`, `sharpen`, `median`, `blur`, `flatten`, `gamma`, `negate`, `normalize`,
|
|
85
|
+
`threshold`, `modulate`, `tint`, `grayscale`, plus the pipeline's `toFormat` and `toBuffer`.
|
|
86
|
+
|
|
87
|
+
- **`sharpen` is the only removal.** Everything else is byte-identical or differs only in added
|
|
88
|
+
upper-bound validation (`extend` now rejects > 10000 px per edge; `extract` now rejects
|
|
89
|
+
> 100000000; `trim` gained an optional `margin` option). None of those changes reject an input
|
|
90
|
+
that `ipx@3.1.1` previously produced valid output for.
|
|
91
|
+
- The `trim` modifier passes a bare number to `sharp.trim()`, which throws
|
|
92
|
+
`Expected object for trim`. That is a **pre-existing upstream IPX bug**, identical under Sharp
|
|
93
|
+
0.34.5 and 0.35.3, and is deliberately left unchanged here so this fork stays a
|
|
94
|
+
security/compatibility fork rather than a behaviour fork.
|
|
95
|
+
|
|
96
|
+
## Verification
|
|
97
|
+
|
|
98
|
+
`scripts/verify-sharpen.mjs` exercises the remap end-to-end against a real image through
|
|
99
|
+
`createIPX` + `ipxFSStorage` and asserts that `sigma`, `flat` and `jagged` actually reach
|
|
100
|
+
libvips (different values must produce different bytes). Run it from a checkout where the
|
|
101
|
+
runtime dependencies resolve:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
node packages/sidecar/ipx/scripts/verify-sharpen.mjs [imageDir]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Running the same script against the unpatched `ipx@3.1.1` dist on `sharp@0.35.3` fails both
|
|
108
|
+
differentiation assertions — every `sharpen` variant returns the identical mild-sharpen buffer —
|
|
109
|
+
which is exactly the silent regression this fork removes.
|
|
110
|
+
|
|
111
|
+
## Maintenance
|
|
112
|
+
|
|
113
|
+
On an upstream `ipx` release, re-vendor `bin/`, `dist/` and `LICENSE`, then re-apply the hunks in
|
|
114
|
+
"Fork delta" (or drop them if upstream has adopted Sharp 0.35). `diff -ru` against the upstream
|
|
115
|
+
tarball must show those hunks and nothing else.
|
package/bin/ipx.mjs
ADDED
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const listhen = require('listhen');
|
|
4
|
+
const citty = require('citty');
|
|
5
|
+
const cli = require('listhen/cli');
|
|
6
|
+
const nodeFs = require('./shared/ipx.GUc23orS.cjs');
|
|
7
|
+
require('defu');
|
|
8
|
+
require('ufo');
|
|
9
|
+
require('h3');
|
|
10
|
+
require('image-meta');
|
|
11
|
+
require('destr');
|
|
12
|
+
require('@fastify/accept-negotiator');
|
|
13
|
+
require('etag');
|
|
14
|
+
require('ofetch');
|
|
15
|
+
require('pathe');
|
|
16
|
+
|
|
17
|
+
const name = "ipx";
|
|
18
|
+
const version = "3.2.0";
|
|
19
|
+
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
20
|
+
|
|
21
|
+
const serve = citty.defineCommand({
|
|
22
|
+
meta: {
|
|
23
|
+
description: "Start IPX Server"
|
|
24
|
+
},
|
|
25
|
+
args: {
|
|
26
|
+
dir: {
|
|
27
|
+
type: "string",
|
|
28
|
+
required: false,
|
|
29
|
+
description: "Directory to serve (default: current directory) ENV: IPX_FS_DIR"
|
|
30
|
+
},
|
|
31
|
+
domains: {
|
|
32
|
+
type: "string",
|
|
33
|
+
required: false,
|
|
34
|
+
description: "Allowed domains (comma separated) ENV: IPX_HTTP_DOMAINS"
|
|
35
|
+
},
|
|
36
|
+
...cli.getArgs()
|
|
37
|
+
},
|
|
38
|
+
async run({ args }) {
|
|
39
|
+
const ipx = nodeFs.createIPX({
|
|
40
|
+
storage: nodeFs.ipxFSStorage({
|
|
41
|
+
dir: args.dir
|
|
42
|
+
}),
|
|
43
|
+
httpStorage: nodeFs.ipxHttpStorage({
|
|
44
|
+
domains: args.domains
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
await listhen.listen(nodeFs.createIPXNodeServer(ipx), {
|
|
48
|
+
name: "IPX",
|
|
49
|
+
...cli.parseArgs(args)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const main = citty.defineCommand({
|
|
54
|
+
meta: {
|
|
55
|
+
name,
|
|
56
|
+
version,
|
|
57
|
+
description
|
|
58
|
+
},
|
|
59
|
+
subCommands: {
|
|
60
|
+
serve
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
citty.runMain(main);
|
package/dist/cli.d.cts
ADDED
package/dist/cli.d.mts
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { listen } from 'listhen';
|
|
2
|
+
import { defineCommand, runMain } from 'citty';
|
|
3
|
+
import { getArgs, parseArgs } from 'listhen/cli';
|
|
4
|
+
import { c as createIPX, i as ipxHttpStorage, g as ipxFSStorage, e as createIPXNodeServer } from './shared/ipx.CXJeaylD.mjs';
|
|
5
|
+
import 'defu';
|
|
6
|
+
import 'ufo';
|
|
7
|
+
import 'h3';
|
|
8
|
+
import 'image-meta';
|
|
9
|
+
import 'destr';
|
|
10
|
+
import '@fastify/accept-negotiator';
|
|
11
|
+
import 'etag';
|
|
12
|
+
import 'ofetch';
|
|
13
|
+
import 'pathe';
|
|
14
|
+
|
|
15
|
+
const name = "ipx";
|
|
16
|
+
const version = "3.2.0";
|
|
17
|
+
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
18
|
+
|
|
19
|
+
const serve = defineCommand({
|
|
20
|
+
meta: {
|
|
21
|
+
description: "Start IPX Server"
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
dir: {
|
|
25
|
+
type: "string",
|
|
26
|
+
required: false,
|
|
27
|
+
description: "Directory to serve (default: current directory) ENV: IPX_FS_DIR"
|
|
28
|
+
},
|
|
29
|
+
domains: {
|
|
30
|
+
type: "string",
|
|
31
|
+
required: false,
|
|
32
|
+
description: "Allowed domains (comma separated) ENV: IPX_HTTP_DOMAINS"
|
|
33
|
+
},
|
|
34
|
+
...getArgs()
|
|
35
|
+
},
|
|
36
|
+
async run({ args }) {
|
|
37
|
+
const ipx = createIPX({
|
|
38
|
+
storage: ipxFSStorage({
|
|
39
|
+
dir: args.dir
|
|
40
|
+
}),
|
|
41
|
+
httpStorage: ipxHttpStorage({
|
|
42
|
+
domains: args.domains
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
await listen(createIPXNodeServer(ipx), {
|
|
46
|
+
name: "IPX",
|
|
47
|
+
...parseArgs(args)
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const main = defineCommand({
|
|
52
|
+
meta: {
|
|
53
|
+
name,
|
|
54
|
+
version,
|
|
55
|
+
description
|
|
56
|
+
},
|
|
57
|
+
subCommands: {
|
|
58
|
+
serve
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
runMain(main);
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const nodeFs = require('./shared/ipx.GUc23orS.cjs');
|
|
4
|
+
const h3 = require('h3');
|
|
5
|
+
require('defu');
|
|
6
|
+
require('ufo');
|
|
7
|
+
require('image-meta');
|
|
8
|
+
require('destr');
|
|
9
|
+
require('@fastify/accept-negotiator');
|
|
10
|
+
require('etag');
|
|
11
|
+
require('ofetch');
|
|
12
|
+
require('pathe');
|
|
13
|
+
|
|
14
|
+
function unstorageToIPXStorage(storage, _options = {}) {
|
|
15
|
+
const options = typeof _options === "string" ? { prefix: _options } : _options;
|
|
16
|
+
const resolveKey = (id) => options.prefix ? `${options.prefix}:${id}` : id;
|
|
17
|
+
return {
|
|
18
|
+
name: "ipx:" + (storage.name || "unstorage"),
|
|
19
|
+
async getMeta(id, opts = {}) {
|
|
20
|
+
if (!storage.getMeta) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const storageKey = resolveKey(id);
|
|
24
|
+
const meta = await storage.getMeta(storageKey, opts);
|
|
25
|
+
return meta;
|
|
26
|
+
},
|
|
27
|
+
async getData(id, opts = {}) {
|
|
28
|
+
if (!storage.getItemRaw) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const storageKey = resolveKey(id);
|
|
32
|
+
let data = await storage.getItemRaw(storageKey, opts);
|
|
33
|
+
if (!data) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (data instanceof Blob) {
|
|
37
|
+
data = await data.arrayBuffer();
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return Buffer.from(data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw h3.createError({
|
|
43
|
+
statusCode: 500,
|
|
44
|
+
statusText: `IPX_STORAGE_ERROR`,
|
|
45
|
+
message: `Failed to parse storage data to Buffer:
|
|
46
|
+
${error.message}`,
|
|
47
|
+
cause: error
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
exports.createIPX = nodeFs.createIPX;
|
|
55
|
+
exports.createIPXH3App = nodeFs.createIPXH3App;
|
|
56
|
+
exports.createIPXH3Handler = nodeFs.createIPXH3Handler;
|
|
57
|
+
exports.createIPXNodeServer = nodeFs.createIPXNodeServer;
|
|
58
|
+
exports.createIPXPlainServer = nodeFs.createIPXPlainServer;
|
|
59
|
+
exports.createIPXWebServer = nodeFs.createIPXWebServer;
|
|
60
|
+
exports.ipxFSStorage = nodeFs.ipxFSStorage;
|
|
61
|
+
exports.ipxHttpStorage = nodeFs.ipxHttpStorage;
|
|
62
|
+
exports.unstorageToIPXStorage = unstorageToIPXStorage;
|