print-check-cli 1.0.0 → 1.0.2
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/README.md +87 -44
- package/dist/index.js +22 -24
- package/package.json +26 -3
package/README.md
CHANGED
|
@@ -1,23 +1,64 @@
|
|
|
1
1
|
# print-check-cli
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/print-check-cli)
|
|
3
4
|
[](https://github.com/ryancalacsan/print-check-cli/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/ryancalacsan/print-check-cli)
|
|
4
6
|
|
|
5
7
|
A Node.js + TypeScript CLI tool that validates print-ready PDF files. Runs eight checks and reports pass/warn/fail results in the terminal.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
**[View on npm](https://www.npmjs.com/package/print-check-cli)**
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|---|---|
|
|
11
|
-
| **Bleed & Trim** | TrimBox/BleedBox presence and minimum bleed dimensions |
|
|
12
|
-
| **Fonts** | Font embedding status (embedded, subset, or missing) |
|
|
13
|
-
| **Color Space** | CMYK compliance, RGB detection, spot color reporting |
|
|
14
|
-
| **Resolution** | Raster image DPI against a configurable minimum |
|
|
15
|
-
| **PDF/X Compliance** | PDF/X standard detection (OutputIntents, version, output condition) — info only |
|
|
16
|
-
| **Total Ink Coverage** | Maximum ink density (C+M+Y+K %) against configurable limit |
|
|
17
|
-
| **Transparency** | Detects unflattened transparency (groups, soft masks, blend modes) |
|
|
18
|
-
| **Page Size** | Verifies consistent page dimensions and optional expected size match |
|
|
11
|
+

|
|
19
12
|
|
|
20
|
-
##
|
|
13
|
+
## Demo
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **8 prepress checks** — bleed/trim boxes, font embedding, color space (CMYK/RGB), image DPI, PDF/X compliance, total ink coverage, transparency, and page size consistency
|
|
20
|
+
- **Built-in print profiles** — presets for standard, magazine, newspaper, and large-format workflows with one `--profile` flag
|
|
21
|
+
- **Per-check severity overrides** — downgrade failures to warnings or skip checks entirely with `--severity fonts:warn,transparency:off`
|
|
22
|
+
- **Multi-file support** — validate entire directories with shell globbing (`print-check *.pdf`)
|
|
23
|
+
- **JSON output for CI** — structured JSON reports via `--format json` for pipeline integration
|
|
24
|
+
- **RC file configuration** — set project defaults in `.printcheckrc`, `.printcheckrc.json`, or `printcheck.config.js` with auto-discovery
|
|
25
|
+
- **Dual PDF engine** — combines mupdf (WASM-powered deep object traversal) with pdf-lib (page box reading) for thorough analysis
|
|
26
|
+
- **Colorized terminal output** — clear pass/warn/fail results with verbose per-page detail mode
|
|
27
|
+
|
|
28
|
+
| Check | What it validates |
|
|
29
|
+
| ---------------------- | ------------------------------------------------------------------------------- |
|
|
30
|
+
| **Bleed & Trim** | TrimBox/BleedBox presence and minimum bleed dimensions |
|
|
31
|
+
| **Fonts** | Font embedding status (embedded, subset, or missing) |
|
|
32
|
+
| **Color Space** | CMYK compliance, RGB detection, spot color reporting |
|
|
33
|
+
| **Resolution** | Raster image DPI against a configurable minimum |
|
|
34
|
+
| **PDF/X Compliance** | PDF/X standard detection (OutputIntents, version, output condition) — info only |
|
|
35
|
+
| **Total Ink Coverage** | Maximum ink density (C+M+Y+K %) against configurable limit |
|
|
36
|
+
| **Transparency** | Detects unflattened transparency (groups, soft masks, blend modes) |
|
|
37
|
+
| **Page Size** | Verifies consistent page dimensions and optional expected size match |
|
|
38
|
+
|
|
39
|
+
## Tech Stack
|
|
40
|
+
|
|
41
|
+
| Package | Purpose |
|
|
42
|
+
| ------------------------------------------------------- | ----------------------------------------------------------- |
|
|
43
|
+
| [mupdf](https://www.npmjs.com/package/mupdf) (mupdf.js) | PDF engine — WASM-powered, deep PDF object traversal |
|
|
44
|
+
| [pdf-lib](https://www.npmjs.com/package/pdf-lib) | Supplemental — reading page boxes (TrimBox, BleedBox, etc.) |
|
|
45
|
+
| [commander](https://www.npmjs.com/package/commander) | CLI framework |
|
|
46
|
+
| [picocolors](https://www.npmjs.com/package/picocolors) | Terminal colors |
|
|
47
|
+
| [zod](https://www.npmjs.com/package/zod) | CLI option validation |
|
|
48
|
+
| [tsup](https://www.npmjs.com/package/tsup) | TypeScript build |
|
|
49
|
+
| [vitest](https://www.npmjs.com/package/vitest) | Testing |
|
|
50
|
+
|
|
51
|
+
## Getting Started
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Install globally
|
|
55
|
+
npm install -g print-check-cli
|
|
56
|
+
|
|
57
|
+
# Or run directly with npx
|
|
58
|
+
npx print-check-cli flyer.pdf
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Usage
|
|
21
62
|
|
|
22
63
|
```
|
|
23
64
|
print-check <file.pdf ...> [options]
|
|
@@ -75,17 +116,12 @@ print-check *.pdf --format json
|
|
|
75
116
|
|
|
76
117
|
Built-in profiles provide preset thresholds for common print scenarios. Explicit CLI flags override profile defaults.
|
|
77
118
|
|
|
78
|
-
| Profile
|
|
79
|
-
|
|
80
|
-
| `standard`
|
|
81
|
-
| `magazine`
|
|
82
|
-
| `newspaper`
|
|
83
|
-
| `large-format` | 150
|
|
84
|
-
|
|
85
|
-
### Exit codes
|
|
86
|
-
|
|
87
|
-
- `0` — all checks passed (or warned)
|
|
88
|
-
- `1` — one or more checks failed
|
|
119
|
+
| Profile | minDpi | colorSpace | bleedMm | maxTac | Use case |
|
|
120
|
+
| -------------- | ------ | ---------- | ------- | ------ | ---------------------------------- |
|
|
121
|
+
| `standard` | 300 | cmyk | 3 | 300 | General commercial print (default) |
|
|
122
|
+
| `magazine` | 300 | cmyk | 5 | 300 | Magazine / perfect-bound |
|
|
123
|
+
| `newspaper` | 150 | any | 0 | 240 | Newsprint / low-fidelity |
|
|
124
|
+
| `large-format` | 150 | cmyk | 5 | 300 | Banners, posters, signage |
|
|
89
125
|
|
|
90
126
|
### Severity Overrides
|
|
91
127
|
|
|
@@ -102,19 +138,25 @@ print-check flyer.pdf --severity transparency:off
|
|
|
102
138
|
print-check flyer.pdf --severity fonts:warn,transparency:off
|
|
103
139
|
```
|
|
104
140
|
|
|
105
|
-
| Level
|
|
106
|
-
|
|
107
|
-
| `fail` | Default — no change to check result
|
|
141
|
+
| Level | Behavior |
|
|
142
|
+
| ------ | ---------------------------------------------- |
|
|
143
|
+
| `fail` | Default — no change to check result |
|
|
108
144
|
| `warn` | Downgrade any `fail` result to `warn` (exit 0) |
|
|
109
|
-
| `off`
|
|
145
|
+
| `off` | Skip the check entirely |
|
|
110
146
|
|
|
111
147
|
Available check names: `bleed`, `fonts`, `colorspace`, `resolution`, `pdfx`, `tac`, `transparency`, `pagesize`.
|
|
112
148
|
|
|
113
|
-
|
|
149
|
+
### Exit Codes
|
|
150
|
+
|
|
151
|
+
- `0` — all checks passed (or warned)
|
|
152
|
+
- `1` — one or more checks failed
|
|
153
|
+
|
|
154
|
+
### Configuration
|
|
114
155
|
|
|
115
156
|
Create a config file to set default options for your project:
|
|
116
157
|
|
|
117
|
-
|
|
158
|
+
#### `.printcheckrc` / `.printcheckrc.json`
|
|
159
|
+
|
|
118
160
|
```json
|
|
119
161
|
{
|
|
120
162
|
"minDpi": 300,
|
|
@@ -130,7 +172,8 @@ Create a config file to set default options for your project:
|
|
|
130
172
|
}
|
|
131
173
|
```
|
|
132
174
|
|
|
133
|
-
|
|
175
|
+
#### `printcheck.config.js`
|
|
176
|
+
|
|
134
177
|
```js
|
|
135
178
|
export default {
|
|
136
179
|
minDpi: 150,
|
|
@@ -143,18 +186,6 @@ export default {
|
|
|
143
186
|
Config files are auto-discovered from the current directory upward.
|
|
144
187
|
CLI flags always override config file values.
|
|
145
188
|
|
|
146
|
-
## Tech Stack
|
|
147
|
-
|
|
148
|
-
| Package | Purpose |
|
|
149
|
-
|---|---|
|
|
150
|
-
| [mupdf](https://www.npmjs.com/package/mupdf) (mupdf.js) | PDF engine — WASM-powered, deep PDF object traversal |
|
|
151
|
-
| [pdf-lib](https://www.npmjs.com/package/pdf-lib) | Supplemental — reading page boxes (TrimBox, BleedBox, etc.) |
|
|
152
|
-
| [commander](https://www.npmjs.com/package/commander) | CLI framework |
|
|
153
|
-
| [picocolors](https://www.npmjs.com/package/picocolors) | Terminal colors |
|
|
154
|
-
| [zod](https://www.npmjs.com/package/zod) | CLI option validation |
|
|
155
|
-
| [tsup](https://www.npmjs.com/package/tsup) | TypeScript build |
|
|
156
|
-
| [vitest](https://www.npmjs.com/package/vitest) | Testing |
|
|
157
|
-
|
|
158
189
|
## Project Structure
|
|
159
190
|
|
|
160
191
|
```
|
|
@@ -182,13 +213,25 @@ src/
|
|
|
182
213
|
## Development
|
|
183
214
|
|
|
184
215
|
```bash
|
|
185
|
-
npm install # Install dependencies
|
|
216
|
+
npm install # Install dependencies (also sets up pre-commit hooks)
|
|
186
217
|
npm run dev -- <file> # Run via tsx (no build needed)
|
|
187
218
|
npm run build # Build to dist/
|
|
188
219
|
npm test # Run vitest
|
|
220
|
+
npm run test:coverage # Run with coverage report
|
|
221
|
+
npm run lint # ESLint
|
|
222
|
+
npm run format:check # Prettier check
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for full development guidelines.
|
|
226
|
+
|
|
227
|
+
## Deployment
|
|
228
|
+
|
|
229
|
+
Published to npm via GitHub Actions with [OIDC trusted publishing](https://docs.npmjs.com/generating-provenance-statements). Every release includes a verified provenance attestation.
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm install -g print-check-cli
|
|
189
233
|
```
|
|
190
234
|
|
|
191
235
|
## Known Limitations (MVP)
|
|
192
236
|
|
|
193
237
|
- **mupdf PDFObject nulls** — mupdf.js returns PDFObject wrappers with `.isNull() === true` rather than JavaScript `null`. All mupdf access goes through `src/engine/pdf-utils.ts` safe wrappers to handle this.
|
|
194
|
-
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import { z as z2 } from "zod";
|
|
6
6
|
import * as fs3 from "fs";
|
|
7
7
|
import * as path2 from "path";
|
|
8
|
+
import { createRequire } from "module";
|
|
8
9
|
|
|
9
10
|
// src/checks/bleed-trim.ts
|
|
10
11
|
var PT_TO_MM = 25.4 / 72;
|
|
@@ -49,11 +50,9 @@ var checkBleedTrim = async (engines, options) => {
|
|
|
49
50
|
if (minBleed < requiredMm) {
|
|
50
51
|
const sides = [];
|
|
51
52
|
if (bleedLeft < requiredMm) sides.push(`left: ${bleedLeft.toFixed(1)}mm`);
|
|
52
|
-
if (bleedRight < requiredMm)
|
|
53
|
-
sides.push(`right: ${bleedRight.toFixed(1)}mm`);
|
|
53
|
+
if (bleedRight < requiredMm) sides.push(`right: ${bleedRight.toFixed(1)}mm`);
|
|
54
54
|
if (bleedTop < requiredMm) sides.push(`top: ${bleedTop.toFixed(1)}mm`);
|
|
55
|
-
if (bleedBottom < requiredMm)
|
|
56
|
-
sides.push(`bottom: ${bleedBottom.toFixed(1)}mm`);
|
|
55
|
+
if (bleedBottom < requiredMm) sides.push(`bottom: ${bleedBottom.toFixed(1)}mm`);
|
|
57
56
|
details.push({
|
|
58
57
|
page: pageNum,
|
|
59
58
|
message: `Insufficient bleed (need ${requiredMm}mm): ${sides.join(", ")}`,
|
|
@@ -109,7 +108,7 @@ function collectFonts(fontDict, pageNum, seen) {
|
|
|
109
108
|
safeForEach(fontDict, (value, key) => {
|
|
110
109
|
const font = safeResolve(value);
|
|
111
110
|
if (!font) return;
|
|
112
|
-
const baseFontName = safeName(safeGet(font, "BaseFont")) ?? key;
|
|
111
|
+
const baseFontName = safeName(safeGet(font, "BaseFont")) ?? String(key);
|
|
113
112
|
if (seen.has(baseFontName)) return;
|
|
114
113
|
seen.add(baseFontName);
|
|
115
114
|
const subtype = safeName(safeGet(font, "Subtype"));
|
|
@@ -703,10 +702,7 @@ import * as mupdf5 from "mupdf";
|
|
|
703
702
|
import { PDFDocument as PDFDocument2 } from "pdf-lib";
|
|
704
703
|
async function loadPdf(filePath) {
|
|
705
704
|
const buffer = fs.readFileSync(filePath);
|
|
706
|
-
const mupdfDoc = mupdf5.PDFDocument.openDocument(
|
|
707
|
-
buffer,
|
|
708
|
-
"application/pdf"
|
|
709
|
-
);
|
|
705
|
+
const mupdfDoc = mupdf5.PDFDocument.openDocument(buffer, "application/pdf");
|
|
710
706
|
const pdfLibDoc = await PDFDocument2.load(buffer, {
|
|
711
707
|
ignoreEncryption: true
|
|
712
708
|
});
|
|
@@ -768,12 +764,7 @@ function buildJsonReport(fileName, results) {
|
|
|
768
764
|
}
|
|
769
765
|
|
|
770
766
|
// src/profiles.ts
|
|
771
|
-
var PROFILE_NAMES = [
|
|
772
|
-
"standard",
|
|
773
|
-
"magazine",
|
|
774
|
-
"newspaper",
|
|
775
|
-
"large-format"
|
|
776
|
-
];
|
|
767
|
+
var PROFILE_NAMES = ["standard", "magazine", "newspaper", "large-format"];
|
|
777
768
|
var PROFILES = {
|
|
778
769
|
standard: { minDpi: 300, colorSpace: "cmyk", bleedMm: 3, maxTac: 300, pageSize: void 0 },
|
|
779
770
|
magazine: { minDpi: 300, colorSpace: "cmyk", bleedMm: 5, maxTac: 300, pageSize: void 0 },
|
|
@@ -798,11 +789,7 @@ var ConfigSchema = z.object({
|
|
|
798
789
|
profile: z.enum(PROFILE_NAMES).optional(),
|
|
799
790
|
severity: z.record(z.string(), z.enum(["fail", "warn", "off"])).optional()
|
|
800
791
|
});
|
|
801
|
-
var CONFIG_FILES = [
|
|
802
|
-
".printcheckrc",
|
|
803
|
-
".printcheckrc.json",
|
|
804
|
-
"printcheck.config.js"
|
|
805
|
-
];
|
|
792
|
+
var CONFIG_FILES = [".printcheckrc", ".printcheckrc.json", "printcheck.config.js"];
|
|
806
793
|
function findConfigFile(startDir) {
|
|
807
794
|
let dir = path.resolve(startDir);
|
|
808
795
|
while (true) {
|
|
@@ -839,12 +826,18 @@ async function loadConfig() {
|
|
|
839
826
|
}
|
|
840
827
|
|
|
841
828
|
// src/index.ts
|
|
829
|
+
var require2 = createRequire(import.meta.url);
|
|
830
|
+
var { version } = require2("../package.json");
|
|
831
|
+
var SEVERITY_LEVELS = ["fail", "warn", "off"];
|
|
832
|
+
function isSeverityOverride(level) {
|
|
833
|
+
return SEVERITY_LEVELS.includes(level);
|
|
834
|
+
}
|
|
842
835
|
function parseSeverityString(val) {
|
|
843
836
|
if (!val.trim()) return {};
|
|
844
837
|
const result = {};
|
|
845
838
|
for (const pair of val.split(",")) {
|
|
846
839
|
const [check, level] = pair.split(":").map((s) => s.trim());
|
|
847
|
-
if (check && level) result[check] = level;
|
|
840
|
+
if (check && level && isSeverityOverride(level)) result[check] = level;
|
|
848
841
|
}
|
|
849
842
|
return result;
|
|
850
843
|
}
|
|
@@ -877,14 +870,19 @@ var OptionsSchema = z2.object({
|
|
|
877
870
|
bleed: z2.coerce.number().nonnegative().optional(),
|
|
878
871
|
maxTac: z2.coerce.number().positive().optional(),
|
|
879
872
|
pageSize: z2.string().optional(),
|
|
880
|
-
checks: z2.string().default("all").transform(
|
|
873
|
+
checks: z2.string().default("all").transform(
|
|
874
|
+
(val) => val === "all" ? Object.keys(ALL_CHECKS) : val.split(",").map((s) => s.trim())
|
|
875
|
+
),
|
|
881
876
|
verbose: z2.boolean().default(false),
|
|
882
877
|
format: z2.enum(["text", "json"]).default("text"),
|
|
883
878
|
profile: z2.enum(PROFILE_NAMES).optional(),
|
|
884
|
-
severity: z2.union([
|
|
879
|
+
severity: z2.union([
|
|
880
|
+
z2.string().transform(parseSeverityString),
|
|
881
|
+
z2.record(z2.string(), z2.enum(["fail", "warn", "off"]))
|
|
882
|
+
]).default({})
|
|
885
883
|
});
|
|
886
884
|
var program = new Command();
|
|
887
|
-
program.name("print-check").description("Validate print-ready PDF files").version(
|
|
885
|
+
program.name("print-check").description("Validate print-ready PDF files").version(version).argument("<files...>", "PDF file(s) to check").option("--min-dpi <number>", "Minimum acceptable DPI").option("--color-space <mode>", "Expected color space: cmyk | any").option("--bleed <mm>", "Required bleed in mm").option("--max-tac <percent>", "Maximum total ink coverage %").option("--page-size <WxH>", "Expected page size in mm (e.g. 210x297)").option("--checks <list>", "Comma-separated checks to run", "all").option("--verbose", "Show detailed per-page results", false).option("--format <type>", "Output format: text | json", "text").option("--profile <name>", "Print profile: standard | magazine | newspaper | large-format").option("--severity <overrides>", "Per-check severity: check:level,... (fail|warn|off)").action(async (files, rawOpts) => {
|
|
888
886
|
const config = await loadConfig();
|
|
889
887
|
const stripped = {};
|
|
890
888
|
for (const [key, value] of Object.entries(rawOpts)) {
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "print-check-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"print-check": "./dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsup",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
10
11
|
"dev": "tsx src/index.ts",
|
|
11
|
-
"test": "vitest run"
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
14
|
+
"lint": "eslint src/ tests/",
|
|
15
|
+
"format": "prettier --write .",
|
|
16
|
+
"format:check": "prettier --check .",
|
|
17
|
+
"prepare": "husky"
|
|
12
18
|
},
|
|
13
19
|
"keywords": [
|
|
14
20
|
"pdf",
|
|
@@ -42,11 +48,28 @@
|
|
|
42
48
|
"picocolors": "^1.1.1",
|
|
43
49
|
"zod": "^4.3.6"
|
|
44
50
|
},
|
|
51
|
+
"lint-staged": {
|
|
52
|
+
"*.{ts,js}": [
|
|
53
|
+
"eslint --fix",
|
|
54
|
+
"prettier --write"
|
|
55
|
+
],
|
|
56
|
+
"*.{json,md,yml,yaml}": [
|
|
57
|
+
"prettier --write"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
45
60
|
"devDependencies": {
|
|
46
|
-
"@
|
|
61
|
+
"@eslint/js": "^10.0.1",
|
|
62
|
+
"@types/node": "^26.0.0",
|
|
63
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
64
|
+
"eslint": "^10.5.0",
|
|
65
|
+
"eslint-config-prettier": "^10.1.8",
|
|
66
|
+
"husky": "^9.1.7",
|
|
67
|
+
"lint-staged": "^16.2.7",
|
|
68
|
+
"prettier": "^3.8.1",
|
|
47
69
|
"tsup": "^8.5.1",
|
|
48
70
|
"tsx": "^4.21.0",
|
|
49
71
|
"typescript": "^5.9.3",
|
|
72
|
+
"typescript-eslint": "^8.54.0",
|
|
50
73
|
"vitest": "^4.0.18"
|
|
51
74
|
}
|
|
52
75
|
}
|