@depup/sharp-cli 6.0.0-depup.0 → 6.1.0-depup.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.1.0 (August 31, 2026)
4
+
5
+ - Added a `{path}` output template for preserving directories matched by glob inputs ([#43](https://github.com/vseventer/sharp-cli/issues/43)).
6
+ - Added percentage-based dimensions to the `resize` command ([#49](https://github.com/vseventer/sharp-cli/issues/49)).
7
+ - Updated `sharp` dependency to 0.35.4.
8
+
3
9
  ## 6.0.0 (August 21, 2026)
4
10
 
5
11
  - Added [`--limitInputChannels`](https://sharp.pixelplumbing.com/api-constructor/) input option.
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/sharp-cli
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [sharp-cli](https://www.npmjs.com/package/sharp-cli) @ 6.0.0 |
17
- | Processed | 2026-08-23 |
16
+ | Original | [sharp-cli](https://www.npmjs.com/package/sharp-cli) @ 6.1.0 |
17
+ | Processed | 2026-09-06 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 2 |
20
20
 
package/changes.json CHANGED
@@ -9,6 +9,6 @@
9
9
  "to": "^18.1.0"
10
10
  }
11
11
  },
12
- "timestamp": "2026-08-23T00:28:58.451Z",
12
+ "timestamp": "2026-09-06T01:03:46.036Z",
13
13
  "totalUpdated": 2
14
14
  }
@@ -27,17 +27,31 @@
27
27
  import constants from "../../lib/constants.js";
28
28
  import { pick } from "../../lib/utils.js";
29
29
 
30
+ // Helpers.
31
+ const resolveDimension = (dimension, metadataDimension) => {
32
+ if (dimension == null) return null;
33
+ if (typeof dimension === "string" && dimension.endsWith("%")) {
34
+ if (metadataDimension == null) {
35
+ throw new Error("Input metadata is required to resolve percentage");
36
+ }
37
+ return Math.round(
38
+ (metadataDimension * Number(dimension.slice(0, -1))) / 100,
39
+ );
40
+ }
41
+ return Number(dimension);
42
+ };
43
+
30
44
  // Configure.
31
45
  const positionals = {
32
46
  width: {
33
47
  default: null,
34
- desc: "Pixels wide the resultant image should be",
35
- type: "number",
48
+ desc: "Pixels or percentage wide the resultant image should be",
49
+ type: "string",
36
50
  },
37
51
  height: {
38
52
  default: null,
39
- desc: "Pixels high the resultant image should be",
40
- type: "number",
53
+ desc: "Pixels or percentage high the resultant image should be",
54
+ type: "string",
41
55
  },
42
56
  };
43
57
 
@@ -94,6 +108,10 @@ const builder = (yargs) => {
94
108
  "$0 resize --height 100",
95
109
  "The output will be 100 pixels high, auto-scaled width",
96
110
  )
111
+ .example(
112
+ "$0 resize 50%",
113
+ "The output will be 50% of the input width, auto-scaled height",
114
+ )
97
115
  .example(
98
116
  '$0 resize 200 300 --background rgba(255,255,255,0.5) --fit contain --kernel nearest --position "right top"',
99
117
  "The output will be 200 pixels wide and 300 pixels high containing a nearest-neighbour scaled version contained within the north-east corner of a semi-transparent white canvas",
@@ -132,8 +150,10 @@ const handler = (args) => {
132
150
  // @see https://sharp.pixelplumbing.com/api-resize#resize
133
151
  return args["#queue"].push([
134
152
  "resize",
135
- (sharp) => {
136
- return sharp.resize(args.width, args.height, pick(args, optionNames));
153
+ (sharp, { metadata } = {}) => {
154
+ const width = resolveDimension(args.width, metadata?.width);
155
+ const height = resolveDimension(args.height, metadata?.height);
156
+ return sharp.resize(width, height, pick(args, optionNames));
137
157
  },
138
158
  ]);
139
159
  };
package/lib/convert.js CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  // Standard lib.
25
25
  import { createReadStream } from "node:fs";
26
+ import { mkdir } from "node:fs/promises";
26
27
  import path from "node:path";
27
28
  import { pipeline } from "node:stream/promises";
28
29
 
@@ -55,26 +56,49 @@ const FORMATS = {
55
56
  ".webp": "webp",
56
57
  };
57
58
 
59
+ // Resolve the non-magic portion of a glob to use as its relative path root.
60
+ const getGlobRoot = (pattern) => {
61
+ const absolute = path.resolve(pattern);
62
+ const magicIndex = absolute.search(/[*?[{(]/);
63
+ if (magicIndex === -1) {
64
+ return path.dirname(absolute);
65
+ }
66
+
67
+ const prefix = absolute.slice(0, magicIndex);
68
+ return prefix.endsWith(path.sep)
69
+ ? path.resolve(prefix)
70
+ : path.dirname(path.resolve(prefix));
71
+ };
72
+
58
73
  // Exports.
59
74
  export default {
60
75
  // Convert a list of files.
61
76
  files: async (input, output, context) => {
62
77
  // Resolve files.
63
- const files = input.flatMap((input) => globSync(input, { absolute: true }));
78
+ const files = input.flatMap((pattern) => {
79
+ const root = getGlobRoot(pattern);
80
+ return globSync(pattern, { absolute: true }).map((src) => ({
81
+ root,
82
+ src,
83
+ }));
84
+ });
64
85
  if (files.length === 0) {
65
86
  throw new Error("No input files");
66
87
  }
67
88
 
68
89
  // Process files.
69
90
  const isBatch = files.length > 1;
70
- const promises = files.map((src) => {
91
+ const promises = files.map(({ root, src }) => {
71
92
  const image = sharp(context.options);
72
93
  return pipeline(createReadStream(src), image)
73
94
  .then(() => image.metadata())
74
95
  .then((metadata) => {
75
96
  // Process output as a template.
76
- const parts = path.parse(src);
77
- const regex = /\{(root|dir|base|ext|name)\}/g;
97
+ const parts = {
98
+ ...path.parse(src),
99
+ path: path.relative(root, path.dirname(src)),
100
+ };
101
+ const regex = /\{(root|dir|path|base|ext|name)\}/g;
78
102
  let dest = output;
79
103
  let match;
80
104
  while ((match = regex.exec(output)) !== null) {
@@ -110,7 +134,9 @@ export default {
110
134
  ? transformer
111
135
  .toBuffer({ resolveWithObject: true })
112
136
  .then(({ info }) => info)
113
- : transformer.toFile(dest);
137
+ : mkdir(path.dirname(dest), { recursive: true }).then(() =>
138
+ transformer.toFile(dest),
139
+ );
114
140
  return promise.then((info) => ({
115
141
  input: inputMetadata,
116
142
  output: { ...info, path: dest },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/sharp-cli",
3
- "version": "6.0.0-depup.0",
3
+ "version": "6.1.0-depup.0",
4
4
  "description": "CLI for sharp. (with updated dependencies)",
5
5
  "keywords": [
6
6
  "sharp-cli",
@@ -22,7 +22,10 @@
22
22
  "license": "MIT",
23
23
  "type": "module",
24
24
  "author": "Mark van Seventer <mark@vseventer.com>",
25
- "repository": "vseventer/sharp-cli",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/vseventer/sharp-cli.git"
28
+ },
26
29
  "bin": {
27
30
  "sharp": "bin/cli.js"
28
31
  },
@@ -36,7 +39,7 @@
36
39
  },
37
40
  "dependencies": {
38
41
  "glob": "^13.0.6",
39
- "sharp": "0.35.3",
42
+ "sharp": "0.35.4",
40
43
  "yargs": "^18.1.0"
41
44
  },
42
45
  "devDependencies": {
@@ -51,6 +54,10 @@
51
54
  "sinon": "22.1.x",
52
55
  "tempy": "3.2.x"
53
56
  },
57
+ "overrides": {
58
+ "diff": "8.0.3",
59
+ "serialize-javascript": "7.1.1"
60
+ },
54
61
  "engines": {
55
62
  "node": ">=20.10.0"
56
63
  },
@@ -67,8 +74,8 @@
67
74
  },
68
75
  "depsUpdated": 2,
69
76
  "originalPackage": "sharp-cli",
70
- "originalVersion": "6.0.0",
71
- "processedAt": "2026-08-23T00:29:02.257Z",
77
+ "originalVersion": "6.1.0",
78
+ "processedAt": "2026-09-06T01:03:50.312Z",
72
79
  "smokeTest": "passed"
73
80
  }
74
81
  }