image-convert-cli 1.1.5 → 1.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/CHANGELOG.md +68 -0
- package/LICENSE.md +21 -0
- package/dist/index.js +20 -8
- package/package.json +4 -2
- package/src/cli.ts +1 -1
- package/src/converter.ts +9 -1
- package/src/prompts.ts +41 -9
- package/src/utils/path.ts +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.2.0] - 2026-02-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- SVG support as input format with automatic resize handling
|
|
9
|
+
- Tilde (~) expansion for home directory in file paths
|
|
10
|
+
- CHANGELOG for version tracking
|
|
11
|
+
- Test coverage script (`bun run test:coverage`)
|
|
12
|
+
|
|
13
|
+
### Tests
|
|
14
|
+
- Unit tests for SVG conversion
|
|
15
|
+
- Unit tests for path completion with tilde expansion
|
|
16
|
+
|
|
17
|
+
## [1.1.6] - 2026-02-13
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- MIT license (LICENSE.md)
|
|
21
|
+
- IDE config files to .gitignore
|
|
22
|
+
|
|
23
|
+
## [1.1.5] - 2025-02-10
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Bump version to 1.1.5
|
|
27
|
+
- Improve documentation
|
|
28
|
+
|
|
29
|
+
## [1.1.4] - 2025-02-08
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Bump version to 1.1.4
|
|
33
|
+
|
|
34
|
+
## [1.1.3] - 2025-02-08
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- Use dynamic version from package.json
|
|
38
|
+
|
|
39
|
+
## [1.1.2] - 2025-02-08
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- Auto-update flag and confirmation prompt for self-update
|
|
43
|
+
- Batch conversion mode for directories
|
|
44
|
+
|
|
45
|
+
## [1.1.1] - 2025-02-07
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- Version command to display CLI version
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Exclude assets from npm package
|
|
52
|
+
|
|
53
|
+
## [1.1.0] - 2025-02-07
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- Self-update check command
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
- Demo video and reorganize assets folder
|
|
60
|
+
|
|
61
|
+
## [1.0.0] - 2025-02-07
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
- PNG format support for image conversion
|
|
65
|
+
- Prepare project for npm/bun publishing
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
- Enhance README with installation, usage examples, and development guide
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lukenguyen-me
|
|
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/dist/index.js
CHANGED
|
@@ -6577,6 +6577,7 @@ import { spawn } from "child_process";
|
|
|
6577
6577
|
// src/prompts.ts
|
|
6578
6578
|
import * as readline3 from "readline";
|
|
6579
6579
|
import * as fs3 from "fs";
|
|
6580
|
+
import * as os from "os";
|
|
6580
6581
|
import * as path2 from "path";
|
|
6581
6582
|
|
|
6582
6583
|
// node_modules/@inquirer/core/dist/lib/key.js
|
|
@@ -6723,7 +6724,7 @@ var effectScheduler = {
|
|
|
6723
6724
|
// node_modules/@inquirer/core/dist/lib/use-state.js
|
|
6724
6725
|
function useState(defaultValue) {
|
|
6725
6726
|
return withPointer((pointer) => {
|
|
6726
|
-
const setState = AsyncResource2.bind(function
|
|
6727
|
+
const setState = AsyncResource2.bind(function setState2(newValue) {
|
|
6727
6728
|
if (pointer.get() !== newValue) {
|
|
6728
6729
|
pointer.set(newValue);
|
|
6729
6730
|
handleChange();
|
|
@@ -8322,11 +8323,11 @@ function isSameFormat(sourcePath, targetFormat) {
|
|
|
8322
8323
|
}
|
|
8323
8324
|
return false;
|
|
8324
8325
|
}
|
|
8325
|
-
var
|
|
8326
|
+
var INPUT_FORMATS = ["webp", "jpeg", "jpg", "png", "svg"];
|
|
8326
8327
|
function getImageFilesFromDirectory(dirPath) {
|
|
8327
8328
|
try {
|
|
8328
8329
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
8329
|
-
return entries.filter((entry) => entry.isFile()).map((entry) => path.join(dirPath, entry.name)).filter((filePath) =>
|
|
8330
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path.join(dirPath, entry.name)).filter((filePath) => INPUT_FORMATS.includes(getExtension(filePath)));
|
|
8330
8331
|
} catch {
|
|
8331
8332
|
return [];
|
|
8332
8333
|
}
|
|
@@ -8361,7 +8362,11 @@ async function convertImage(sourcePath, destinationPath, format, compress) {
|
|
|
8361
8362
|
default:
|
|
8362
8363
|
throw new Error(`Unsupported format: ${format}`);
|
|
8363
8364
|
}
|
|
8364
|
-
|
|
8365
|
+
let sharpInstance = import_sharp.default(sourcePath);
|
|
8366
|
+
if (getExtension(sourcePath) === "svg") {
|
|
8367
|
+
sharpInstance = sharpInstance.resize(512, 512, { fit: "inside" });
|
|
8368
|
+
}
|
|
8369
|
+
await sharpInstance.toFormat(sharpFormat, options).toFile(destinationPath);
|
|
8365
8370
|
const outputSize = fs2.statSync(destinationPath).size;
|
|
8366
8371
|
const elapsed = Date.now() - startTime;
|
|
8367
8372
|
return {
|
|
@@ -8474,7 +8479,11 @@ function filePathCompleter(line) {
|
|
|
8474
8479
|
const trimmed = line.trim();
|
|
8475
8480
|
const input = trimmed.split(" ")[0] || ".";
|
|
8476
8481
|
const isDirectoryInput = input.endsWith("/");
|
|
8477
|
-
|
|
8482
|
+
let dir = isDirectoryInput ? input.slice(0, -1) || "." : path2.dirname(input) || ".";
|
|
8483
|
+
if (dir === "~" || dir.startsWith("~/")) {
|
|
8484
|
+
const homeDir = os.homedir();
|
|
8485
|
+
dir = dir === "~" ? homeDir : homeDir + dir.slice(1);
|
|
8486
|
+
}
|
|
8478
8487
|
const base = isDirectoryInput ? "" : path2.basename(input) || "";
|
|
8479
8488
|
try {
|
|
8480
8489
|
const files = fs3.readdirSync(dir, { withFileTypes: true });
|
|
@@ -8599,6 +8608,7 @@ Options:
|
|
|
8599
8608
|
--yes, -y Non-interactive mode (use defaults for optional prompts)
|
|
8600
8609
|
--source, -s Source file path (required with -y)
|
|
8601
8610
|
--format, -f Target format: webp, jpeg, jpg, or png (required with -y)
|
|
8611
|
+
Input formats: webp, jpeg, jpg, png, svg
|
|
8602
8612
|
--dest, -d Destination path (optional, auto-generated if not provided)
|
|
8603
8613
|
--compress, -c Enable compression (optional, default: false)
|
|
8604
8614
|
|
|
@@ -8638,7 +8648,8 @@ Conversion settings:`);
|
|
|
8638
8648
|
// package.json
|
|
8639
8649
|
var package_default = {
|
|
8640
8650
|
name: "image-convert-cli",
|
|
8641
|
-
version: "1.
|
|
8651
|
+
version: "1.2.0",
|
|
8652
|
+
license: "MIT",
|
|
8642
8653
|
type: "module",
|
|
8643
8654
|
bin: {
|
|
8644
8655
|
imgc: "./dist/index.js"
|
|
@@ -8646,6 +8657,7 @@ var package_default = {
|
|
|
8646
8657
|
scripts: {
|
|
8647
8658
|
start: "bun bin/index.ts",
|
|
8648
8659
|
test: "bun test",
|
|
8660
|
+
"test:coverage": "bun test --coverage",
|
|
8649
8661
|
prepare: "husky install",
|
|
8650
8662
|
build: "bun build ./bin/index.ts --outdir ./dist --target bun",
|
|
8651
8663
|
publish: "bun run build && bun publish --access public --ignore-scripts"
|
|
@@ -8658,7 +8670,7 @@ var package_default = {
|
|
|
8658
8670
|
sharp: "^0.34.5"
|
|
8659
8671
|
},
|
|
8660
8672
|
devDependencies: {
|
|
8661
|
-
"@types/bun": "^1.3.
|
|
8673
|
+
"@types/bun": "^1.3.9",
|
|
8662
8674
|
husky: "^9.1.7"
|
|
8663
8675
|
},
|
|
8664
8676
|
repository: {
|
|
@@ -8751,7 +8763,7 @@ async function runCli(options, promptService) {
|
|
|
8751
8763
|
prompts.showHelp();
|
|
8752
8764
|
return;
|
|
8753
8765
|
}
|
|
8754
|
-
console.log(`Image Converter - Convert images
|
|
8766
|
+
console.log(`Image Converter - Convert images between webp, jpeg, jpg, png, and svg
|
|
8755
8767
|
`);
|
|
8756
8768
|
const sourcePath = options.source || await prompts.promptSourceFile();
|
|
8757
8769
|
const targetFormat = options.format || await prompts.promptFormat();
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-convert-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"bin": {
|
|
6
7
|
"imgc": "./dist/index.js"
|
|
@@ -8,6 +9,7 @@
|
|
|
8
9
|
"scripts": {
|
|
9
10
|
"start": "bun bin/index.ts",
|
|
10
11
|
"test": "bun test",
|
|
12
|
+
"test:coverage": "bun test --coverage",
|
|
11
13
|
"prepare": "husky install",
|
|
12
14
|
"build": "bun build ./bin/index.ts --outdir ./dist --target bun",
|
|
13
15
|
"publish": "bun run build && bun publish --access public --ignore-scripts"
|
|
@@ -20,7 +22,7 @@
|
|
|
20
22
|
"sharp": "^0.34.5"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
|
-
"@types/bun": "^1.3.
|
|
25
|
+
"@types/bun": "^1.3.9",
|
|
24
26
|
"husky": "^9.1.7"
|
|
25
27
|
},
|
|
26
28
|
"repository": {
|
package/src/cli.ts
CHANGED
|
@@ -118,7 +118,7 @@ export async function runCli(
|
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
console.log("Image Converter - Convert images
|
|
121
|
+
console.log("Image Converter - Convert images between webp, jpeg, jpg, png, and svg\n");
|
|
122
122
|
|
|
123
123
|
const sourcePath = options.source || await prompts.promptSourceFile();
|
|
124
124
|
|
package/src/converter.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getImageFilesFromDirectory,
|
|
13
13
|
isSameFormat,
|
|
14
14
|
ensureDirectoryExists,
|
|
15
|
+
getExtension,
|
|
15
16
|
} from "./utils/path";
|
|
16
17
|
|
|
17
18
|
export async function convertImage(
|
|
@@ -52,7 +53,14 @@ export async function convertImage(
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// Perform the conversion
|
|
55
|
-
|
|
56
|
+
let sharpInstance = sharp(sourcePath);
|
|
57
|
+
|
|
58
|
+
// For SVG input, resize to get proper output dimensions
|
|
59
|
+
if (getExtension(sourcePath) === "svg") {
|
|
60
|
+
sharpInstance = sharpInstance.resize(512, 512, { fit: "inside" });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await sharpInstance
|
|
56
64
|
.toFormat(sharpFormat, options)
|
|
57
65
|
.toFile(destinationPath);
|
|
58
66
|
|
package/src/prompts.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import * as readline from "node:readline";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import { select, confirm } from "@inquirer/prompts";
|
|
5
6
|
import type { SupportedFormat, ConversionSettings } from "./types";
|
|
6
|
-
import { getDefaultDestinationPath } from "./utils/path";
|
|
7
7
|
import { displayConversionResult } from "./converter";
|
|
8
8
|
|
|
9
|
-
function filePathCompleter(line: string): readline.CompleterResult {
|
|
9
|
+
export function filePathCompleter(line: string): readline.CompleterResult {
|
|
10
10
|
const trimmed = line.trim();
|
|
11
11
|
const input = trimmed.split(" ")[0] || ".";
|
|
12
12
|
|
|
13
13
|
const isDirectoryInput = input.endsWith("/");
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
let dir = isDirectoryInput
|
|
15
|
+
? input.slice(0, -1) || "."
|
|
16
|
+
: path.dirname(input) || ".";
|
|
17
|
+
|
|
18
|
+
// Expand tilde to home directory
|
|
19
|
+
if (dir === "~" || dir.startsWith("~/")) {
|
|
20
|
+
const homeDir = os.homedir();
|
|
21
|
+
dir = dir === "~" ? homeDir : homeDir + dir.slice(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const base = isDirectoryInput ? "" : path.basename(input) || "";
|
|
16
25
|
|
|
17
26
|
try {
|
|
18
27
|
const files: fs.Dirent[] = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -56,7 +65,9 @@ async function inputWithPathCompletion(
|
|
|
56
65
|
const validationResult = validate(result);
|
|
57
66
|
if (validationResult !== true) {
|
|
58
67
|
console.log(`\nError: ${validationResult}`);
|
|
59
|
-
inputWithPathCompletion(message, defaultValue, validate).then(
|
|
68
|
+
inputWithPathCompletion(message, defaultValue, validate).then(
|
|
69
|
+
resolve,
|
|
70
|
+
);
|
|
60
71
|
return;
|
|
61
72
|
}
|
|
62
73
|
}
|
|
@@ -68,16 +79,29 @@ async function inputWithPathCompletion(
|
|
|
68
79
|
|
|
69
80
|
export interface IPromptService {
|
|
70
81
|
promptSourceFile(validate?: (path: string) => string | true): Promise<string>;
|
|
71
|
-
promptDestination(
|
|
82
|
+
promptDestination(
|
|
83
|
+
defaultPath: string,
|
|
84
|
+
validate?: (path: string) => string | true,
|
|
85
|
+
): Promise<string>;
|
|
72
86
|
promptFormat(): Promise<SupportedFormat>;
|
|
73
87
|
promptCompress(): Promise<boolean>;
|
|
74
88
|
promptConfirm(settings: ConversionSettings): Promise<boolean>;
|
|
75
89
|
promptForOverwrite(filePath: string): Promise<boolean>;
|
|
76
|
-
promptBatchDestination(
|
|
90
|
+
promptBatchDestination(
|
|
91
|
+
defaultPath: string,
|
|
92
|
+
validate?: (path: string) => string | true,
|
|
93
|
+
): Promise<string>;
|
|
77
94
|
promptBatchConfirm(fileCount: number): Promise<boolean>;
|
|
78
95
|
showHelp(): void;
|
|
79
96
|
showSettings(settings: ConversionSettings): void;
|
|
80
|
-
showResult(result: {
|
|
97
|
+
showResult(result: {
|
|
98
|
+
success: boolean;
|
|
99
|
+
destinationPath: string;
|
|
100
|
+
elapsed: number;
|
|
101
|
+
originalSize: number;
|
|
102
|
+
outputSize: number;
|
|
103
|
+
error?: string;
|
|
104
|
+
}): void;
|
|
81
105
|
}
|
|
82
106
|
|
|
83
107
|
export class InteractivePromptService implements IPromptService {
|
|
@@ -197,6 +221,7 @@ Options:
|
|
|
197
221
|
--yes, -y Non-interactive mode (use defaults for optional prompts)
|
|
198
222
|
--source, -s Source file path (required with -y)
|
|
199
223
|
--format, -f Target format: webp, jpeg, jpg, or png (required with -y)
|
|
224
|
+
Input formats: webp, jpeg, jpg, png, svg
|
|
200
225
|
--dest, -d Destination path (optional, auto-generated if not provided)
|
|
201
226
|
--compress, -c Enable compression (optional, default: false)
|
|
202
227
|
|
|
@@ -222,7 +247,14 @@ Examples:
|
|
|
222
247
|
console.log(` Compression: ${settings.compress ? "Yes" : "No"}`);
|
|
223
248
|
}
|
|
224
249
|
|
|
225
|
-
showResult(result: {
|
|
250
|
+
showResult(result: {
|
|
251
|
+
success: boolean;
|
|
252
|
+
destinationPath: string;
|
|
253
|
+
elapsed: number;
|
|
254
|
+
originalSize: number;
|
|
255
|
+
outputSize: number;
|
|
256
|
+
error?: string;
|
|
257
|
+
}): void {
|
|
226
258
|
displayConversionResult({
|
|
227
259
|
success: result.success,
|
|
228
260
|
sourcePath: "",
|
package/src/utils/path.ts
CHANGED
|
@@ -33,7 +33,7 @@ export function isSameFormat(sourcePath: string, targetFormat: SupportedFormat):
|
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const
|
|
36
|
+
export const INPUT_FORMATS = ["webp", "jpeg", "jpg", "png", "svg"];
|
|
37
37
|
|
|
38
38
|
export function getImageFilesFromDirectory(dirPath: string): string[] {
|
|
39
39
|
try {
|
|
@@ -41,7 +41,7 @@ export function getImageFilesFromDirectory(dirPath: string): string[] {
|
|
|
41
41
|
return entries
|
|
42
42
|
.filter((entry) => entry.isFile())
|
|
43
43
|
.map((entry) => path.join(dirPath, entry.name))
|
|
44
|
-
.filter((filePath) =>
|
|
44
|
+
.filter((filePath) => INPUT_FORMATS.includes(getExtension(filePath)));
|
|
45
45
|
} catch {
|
|
46
46
|
return [];
|
|
47
47
|
}
|