@infograb/docker-slim-advisor 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/build/base-image-db-loader.d.ts +16 -0
  4. package/build/base-image-db-loader.js +72 -0
  5. package/build/base-image-db-loader.js.map +1 -0
  6. package/build/cli.d.ts +47 -0
  7. package/build/cli.js +142 -0
  8. package/build/cli.js.map +1 -0
  9. package/build/data/base-image-db-schema.d.ts +117 -0
  10. package/build/data/base-image-db-schema.js +108 -0
  11. package/build/data/base-image-db-schema.js.map +1 -0
  12. package/build/data/base-image-db.d.ts +21 -0
  13. package/build/data/base-image-db.js +190 -0
  14. package/build/data/base-image-db.js.map +1 -0
  15. package/build/exit-codes.d.ts +25 -0
  16. package/build/exit-codes.js +31 -0
  17. package/build/exit-codes.js.map +1 -0
  18. package/build/formatters/formatter-dispatcher.d.ts +15 -0
  19. package/build/formatters/formatter-dispatcher.js +27 -0
  20. package/build/formatters/formatter-dispatcher.js.map +1 -0
  21. package/build/formatters/json-formatter.d.ts +10 -0
  22. package/build/formatters/json-formatter.js +52 -0
  23. package/build/formatters/json-formatter.js.map +1 -0
  24. package/build/formatters/json-schema.d.ts +57 -0
  25. package/build/formatters/json-schema.js +13 -0
  26. package/build/formatters/json-schema.js.map +1 -0
  27. package/build/formatters/markdown-formatter.d.ts +12 -0
  28. package/build/formatters/markdown-formatter.js +97 -0
  29. package/build/formatters/markdown-formatter.js.map +1 -0
  30. package/build/formatters/terminal-formatter.d.ts +12 -0
  31. package/build/formatters/terminal-formatter.js +142 -0
  32. package/build/formatters/terminal-formatter.js.map +1 -0
  33. package/build/image-size-lookup.d.ts +35 -0
  34. package/build/image-size-lookup.js +187 -0
  35. package/build/image-size-lookup.js.map +1 -0
  36. package/build/multi-stage-detector.d.ts +29 -0
  37. package/build/multi-stage-detector.js +29 -0
  38. package/build/multi-stage-detector.js.map +1 -0
  39. package/build/output.d.ts +46 -0
  40. package/build/output.js +62 -0
  41. package/build/output.js.map +1 -0
  42. package/build/parser.d.ts +7 -0
  43. package/build/parser.js +123 -0
  44. package/build/parser.js.map +1 -0
  45. package/build/rules/alpine-swap.d.ts +10 -0
  46. package/build/rules/alpine-swap.js +81 -0
  47. package/build/rules/alpine-swap.js.map +1 -0
  48. package/build/rules/dockerignore-missing.d.ts +19 -0
  49. package/build/rules/dockerignore-missing.js +107 -0
  50. package/build/rules/dockerignore-missing.js.map +1 -0
  51. package/build/rules/index.d.ts +11 -0
  52. package/build/rules/index.js +22 -0
  53. package/build/rules/index.js.map +1 -0
  54. package/build/rules/package-cache-cleanup.d.ts +15 -0
  55. package/build/rules/package-cache-cleanup.js +89 -0
  56. package/build/rules/package-cache-cleanup.js.map +1 -0
  57. package/build/rules/run-merge.d.ts +12 -0
  58. package/build/rules/run-merge.js +67 -0
  59. package/build/rules/run-merge.js.map +1 -0
  60. package/build/rules/unnecessary-packages.d.ts +23 -0
  61. package/build/rules/unnecessary-packages.js +184 -0
  62. package/build/rules/unnecessary-packages.js.map +1 -0
  63. package/build/severity-filter.d.ts +22 -0
  64. package/build/severity-filter.js +31 -0
  65. package/build/severity-filter.js.map +1 -0
  66. package/build/size-estimator.d.ts +35 -0
  67. package/build/size-estimator.js +238 -0
  68. package/build/size-estimator.js.map +1 -0
  69. package/build/tty-detection.d.ts +20 -0
  70. package/build/tty-detection.js +33 -0
  71. package/build/tty-detection.js.map +1 -0
  72. package/build/types.d.ts +82 -0
  73. package/build/types.js +6 -0
  74. package/build/types.js.map +1 -0
  75. package/package.json +52 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Size prediction engine — estimates Before/After image sizes from parsed
3
+ * Dockerfile instructions using base image DB lookups + layer heuristics.
4
+ *
5
+ * Strategy:
6
+ * - Base image: direct DB lookup via getImageSize()
7
+ * - RUN layers: heuristic by command type (apt install, pip, npm, etc.)
8
+ * - COPY/ADD: rough estimates based on source patterns
9
+ * - After: subtract recommendation savings from before estimate
10
+ */
11
+ // --- Constants (bytes) ---
12
+ /** Fallback base image size when not found in DB */
13
+ const UNKNOWN_BASE_IMAGE_SIZE = 200_000_000; // 200MB
14
+ /** Base size estimate per package-manager install command */
15
+ const PKG_INSTALL_SIZE = {
16
+ 'apt-get install': 50_000_000,
17
+ 'apt install': 50_000_000,
18
+ 'apk add': 15_000_000,
19
+ 'yum install': 60_000_000,
20
+ 'dnf install': 60_000_000,
21
+ 'pip install': 30_000_000,
22
+ 'pip3 install': 30_000_000,
23
+ 'npm install': 80_000_000,
24
+ 'npm ci': 80_000_000,
25
+ 'yarn install': 80_000_000,
26
+ 'yarn add': 40_000_000,
27
+ 'pnpm install': 60_000_000,
28
+ 'gem install': 20_000_000,
29
+ 'go build': 30_000_000,
30
+ 'go install': 30_000_000,
31
+ 'cargo build': 100_000_000,
32
+ 'cargo install': 50_000_000,
33
+ 'composer install': 40_000_000,
34
+ 'bundle install': 50_000_000,
35
+ };
36
+ /** Average per-package size by package manager prefix */
37
+ const PER_PACKAGE_SIZE = {
38
+ 'apt-get': 15_000_000,
39
+ 'apt': 15_000_000,
40
+ 'apk': 5_000_000,
41
+ 'yum': 20_000_000,
42
+ 'dnf': 20_000_000,
43
+ };
44
+ /** Override sizes for known heavy packages */
45
+ const HEAVY_PACKAGES = {
46
+ 'build-essential': 250_000_000,
47
+ 'gcc': 150_000_000,
48
+ 'g++': 150_000_000,
49
+ 'cmake': 40_000_000,
50
+ 'git': 40_000_000,
51
+ 'curl': 10_000_000,
52
+ 'wget': 8_000_000,
53
+ 'python3': 50_000_000,
54
+ 'python3-dev': 30_000_000,
55
+ 'openjdk-17-jdk': 350_000_000,
56
+ 'openjdk-21-jdk': 370_000_000,
57
+ 'nodejs': 80_000_000,
58
+ 'default-jdk': 350_000_000,
59
+ 'libssl-dev': 10_000_000,
60
+ };
61
+ /** Estimated savings from cache cleanup (apt-get clean, etc.) */
62
+ export const APT_CACHE_CLEANUP_SAVINGS = 30_000_000;
63
+ export const APK_CACHE_CLEANUP_SAVINGS = 10_000_000;
64
+ const DEFAULT_COPY_SIZE = 10_000_000; // 10MB
65
+ const GENERIC_CMD_SIZE = 1_000_000; // 1MB fallback per command
66
+ // --- Public API ---
67
+ /**
68
+ * Estimate the "before" (current) image size from parsed instructions.
69
+ */
70
+ export function estimateBeforeSize(instructions, getImageSizeFn) {
71
+ const fromInstr = instructions.find((i) => i.type === 'FROM');
72
+ if (!fromInstr) {
73
+ return { baseImageBytes: 0, layerBytes: 0, totalBytes: 0 };
74
+ }
75
+ const baseImageBytes = getImageSizeFn(fromInstr.image, fromInstr.tag)
76
+ ?? UNKNOWN_BASE_IMAGE_SIZE;
77
+ let layerBytes = 0;
78
+ for (const instr of instructions) {
79
+ switch (instr.type) {
80
+ case 'RUN':
81
+ layerBytes += estimateRunLayerSize(instr);
82
+ break;
83
+ case 'COPY':
84
+ layerBytes += estimateCopyLayerSize(instr);
85
+ break;
86
+ case 'ADD':
87
+ layerBytes += estimateAddLayerSize(instr);
88
+ break;
89
+ }
90
+ }
91
+ return { baseImageBytes, layerBytes, totalBytes: baseImageBytes + layerBytes };
92
+ }
93
+ /**
94
+ * Estimate the "after" (optimized) image size by subtracting recommendation savings.
95
+ * If a base-image swap recommendation exists, uses the alternative base size.
96
+ */
97
+ export function estimateAfterSize(beforeSize, recommendations, newBaseImageBytes) {
98
+ const baseSwapRec = recommendations.find((r) => r.ruleId === 'DSA001');
99
+ const adjustedBase = baseSwapRec && newBaseImageBytes !== undefined
100
+ ? newBaseImageBytes
101
+ : beforeSize.baseImageBytes;
102
+ // Layer savings = total savings minus base-image swap savings
103
+ const totalSavings = recommendations.reduce((s, r) => s + r.estimatedSavingsBytes, 0);
104
+ const layerSavings = baseSwapRec
105
+ ? totalSavings - baseSwapRec.estimatedSavingsBytes
106
+ : totalSavings;
107
+ const newLayerBytes = Math.max(0, beforeSize.layerBytes - layerSavings);
108
+ return {
109
+ baseImageBytes: adjustedBase,
110
+ layerBytes: newLayerBytes,
111
+ totalBytes: adjustedBase + newLayerBytes,
112
+ };
113
+ }
114
+ /** Calculate size reduction percentage (0 if beforeBytes is 0) */
115
+ export function calcSizeReductionPercent(beforeBytes, afterBytes) {
116
+ if (beforeBytes === 0)
117
+ return 0;
118
+ return Math.round(((beforeBytes - afterBytes) / beforeBytes) * 1000) / 10;
119
+ }
120
+ /**
121
+ * Estimate cache cleanup savings for a RUN command string.
122
+ * Used by optimization rules to populate estimatedSavingsBytes.
123
+ */
124
+ export function estimateCacheCleanupSavings(command) {
125
+ const lower = command.toLowerCase();
126
+ const hasCleanup = CLEANUP_PATTERNS.some((p) => lower.includes(p));
127
+ if (hasCleanup)
128
+ return 0; // Already cleaning
129
+ if (lower.includes('apt-get install') || lower.includes('apt install')) {
130
+ return APT_CACHE_CLEANUP_SAVINGS;
131
+ }
132
+ if (lower.includes('apk add'))
133
+ return APK_CACHE_CLEANUP_SAVINGS;
134
+ return 0;
135
+ }
136
+ // --- Internal Helpers ---
137
+ /** Split RUN command by && or ; into individual subcommands */
138
+ function splitCommands(command) {
139
+ return command
140
+ .split(/\s*(?:&&|;)\s*/)
141
+ .map((c) => c.trim())
142
+ .filter((c) => c.length > 0);
143
+ }
144
+ /** Estimate size of a RUN instruction's layer */
145
+ function estimateRunLayerSize(instr) {
146
+ const commands = splitCommands(instr.command);
147
+ let size = 0;
148
+ for (const cmd of commands) {
149
+ const lower = cmd.toLowerCase();
150
+ // Skip cache cleanup commands (they reduce, not add)
151
+ if (isCacheCleanup(lower))
152
+ continue;
153
+ // Match package manager install patterns
154
+ let matched = false;
155
+ for (const [pattern, baseSize] of Object.entries(PKG_INSTALL_SIZE)) {
156
+ if (lower.includes(pattern)) {
157
+ const pkgCount = countPackages(lower, pattern);
158
+ if (pkgCount > 0) {
159
+ const mgr = pattern.split(' ')[0];
160
+ size += estimatePackagesSize(lower, pkgCount, PER_PACKAGE_SIZE[mgr] ?? 10_000_000);
161
+ }
162
+ else {
163
+ size += baseSize;
164
+ }
165
+ matched = true;
166
+ break;
167
+ }
168
+ }
169
+ if (!matched) {
170
+ size += GENERIC_CMD_SIZE;
171
+ }
172
+ }
173
+ return size;
174
+ }
175
+ /** Count package names after an install command (excluding flags) */
176
+ function countPackages(cmd, installPattern) {
177
+ const afterInstall = cmd.split(installPattern)[1] || '';
178
+ return afterInstall
179
+ .split(/\s+/)
180
+ .filter((t) => t.length > 0 && !t.startsWith('-') && !t.includes('='))
181
+ .length;
182
+ }
183
+ /** Estimate total size from known heavy packages + default per-package */
184
+ function estimatePackagesSize(cmd, count, defaultPerPkg) {
185
+ let total = 0;
186
+ let heavyMatched = 0;
187
+ for (const [pkg, size] of Object.entries(HEAVY_PACKAGES)) {
188
+ if (cmd.includes(pkg)) {
189
+ total += size;
190
+ heavyMatched++;
191
+ }
192
+ }
193
+ const remaining = Math.max(0, count - heavyMatched);
194
+ total += remaining * defaultPerPkg;
195
+ return total;
196
+ }
197
+ const CLEANUP_PATTERNS = [
198
+ 'apt-get clean',
199
+ 'apt-get autoremove',
200
+ 'rm -rf /var/lib/apt/lists',
201
+ 'rm -rf /var/cache/apt',
202
+ 'apk cache clean',
203
+ 'yum clean all',
204
+ 'dnf clean all',
205
+ 'rm -rf /tmp/',
206
+ 'rm -rf /root/.cache',
207
+ 'pip cache purge',
208
+ 'npm cache clean',
209
+ 'yarn cache clean',
210
+ ];
211
+ function isCacheCleanup(cmd) {
212
+ return CLEANUP_PATTERNS.some((p) => cmd.includes(p));
213
+ }
214
+ /** Estimate COPY layer size from source pattern */
215
+ function estimateCopyLayerSize(instr) {
216
+ if (instr.from)
217
+ return DEFAULT_COPY_SIZE / 2; // multi-stage copy
218
+ const src = instr.source;
219
+ if (src.includes('package') || src.includes('.json') || src.includes('.lock')) {
220
+ return 500_000; // config/lock files ~500KB
221
+ }
222
+ if (src === '.' || src === './') {
223
+ return 50_000_000; // entire project ~50MB
224
+ }
225
+ return DEFAULT_COPY_SIZE;
226
+ }
227
+ /** Estimate ADD layer size from source pattern */
228
+ function estimateAddLayerSize(instr) {
229
+ const src = instr.source;
230
+ if (src.startsWith('http://') || src.startsWith('https://')) {
231
+ return 20_000_000; // remote download ~20MB
232
+ }
233
+ if (src.endsWith('.tar.gz') || src.endsWith('.tgz') || src.endsWith('.tar')) {
234
+ return 50_000_000; // archive ~50MB
235
+ }
236
+ return DEFAULT_COPY_SIZE;
237
+ }
238
+ //# sourceMappingURL=size-estimator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"size-estimator.js","sourceRoot":"","sources":["../src/size-estimator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Centralized TTY detection for output formatting.
3
+ *
4
+ * Rules:
5
+ * - Emoji and color only when stdout.isTTY === true
6
+ * - NO_COLOR env var (any value) disables color per https://no-color.org/
7
+ * - FORCE_COLOR env var overrides TTY check (for CI systems that support color)
8
+ * - Non-TTY (piped) output gets plain text only
9
+ */
10
+ /** Determine if colored output should be used */
11
+ export declare function shouldUseColor(): boolean;
12
+ /** Determine if emoji should be used (TTY only, NO_COLOR does not affect emoji) */
13
+ export declare function shouldUseEmoji(): boolean;
14
+ /** Combined output capabilities for formatters */
15
+ export interface OutputCapabilities {
16
+ color: boolean;
17
+ emoji: boolean;
18
+ }
19
+ /** Get all output capabilities in a single call */
20
+ export declare function getOutputCapabilities(): OutputCapabilities;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Centralized TTY detection for output formatting.
3
+ *
4
+ * Rules:
5
+ * - Emoji and color only when stdout.isTTY === true
6
+ * - NO_COLOR env var (any value) disables color per https://no-color.org/
7
+ * - FORCE_COLOR env var overrides TTY check (for CI systems that support color)
8
+ * - Non-TTY (piped) output gets plain text only
9
+ */
10
+ /** Determine if colored output should be used */
11
+ export function shouldUseColor() {
12
+ // NO_COLOR takes highest priority per spec — any value disables color
13
+ if (process.env['NO_COLOR'] !== undefined)
14
+ return false;
15
+ // FORCE_COLOR overrides TTY detection (used by some CI systems)
16
+ if (process.env['FORCE_COLOR'] !== undefined && process.env['FORCE_COLOR'] !== '0')
17
+ return true;
18
+ // Default: color only when stdout is a TTY
19
+ return process.stdout.isTTY === true;
20
+ }
21
+ /** Determine if emoji should be used (TTY only, NO_COLOR does not affect emoji) */
22
+ export function shouldUseEmoji() {
23
+ // Emoji only renders correctly in interactive terminals
24
+ return process.stdout.isTTY === true;
25
+ }
26
+ /** Get all output capabilities in a single call */
27
+ export function getOutputCapabilities() {
28
+ return {
29
+ color: shouldUseColor(),
30
+ emoji: shouldUseEmoji(),
31
+ };
32
+ }
33
+ //# sourceMappingURL=tty-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tty-detection.js","sourceRoot":"","sources":["../src/tty-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iDAAiD;AACjD,MAAM,UAAU,cAAc;IAC5B,sEAAsE;IACtE,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAExD,gEAAgE;IAChE,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAEhG,2CAA2C;IAC3C,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;AACvC,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc;IAC5B,wDAAwD;IACxD,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;AACvC,CAAC;AAQD,mDAAmD;AACnD,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,KAAK,EAAE,cAAc,EAAE;QACvB,KAAK,EAAE,cAAc,EAAE;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Typed AST for parsed Dockerfile instructions.
3
+ * Serves as intermediate representation between parser and rules.
4
+ */
5
+ export type InstructionType = 'FROM' | 'RUN' | 'COPY' | 'ADD' | 'ENV' | 'EXPOSE' | 'WORKDIR' | 'CMD' | 'ENTRYPOINT' | 'LABEL' | 'ARG' | 'VOLUME' | 'USER' | 'HEALTHCHECK' | 'SHELL' | 'STOPSIGNAL' | 'ONBUILD' | 'UNKNOWN';
6
+ /** Base instruction with line number and raw text */
7
+ export interface BaseInstruction {
8
+ type: InstructionType;
9
+ line: number;
10
+ raw: string;
11
+ }
12
+ export interface FromInstruction extends BaseInstruction {
13
+ type: 'FROM';
14
+ image: string;
15
+ tag: string;
16
+ alias?: string;
17
+ }
18
+ export interface RunInstruction extends BaseInstruction {
19
+ type: 'RUN';
20
+ command: string;
21
+ /** Whether this was a multi-line command joined from backslash continuations */
22
+ isMultiLine: boolean;
23
+ }
24
+ export interface CopyInstruction extends BaseInstruction {
25
+ type: 'COPY';
26
+ source: string;
27
+ destination: string;
28
+ from?: string;
29
+ }
30
+ export interface AddInstruction extends BaseInstruction {
31
+ type: 'ADD';
32
+ source: string;
33
+ destination: string;
34
+ }
35
+ export interface EnvInstruction extends BaseInstruction {
36
+ type: 'ENV';
37
+ key: string;
38
+ value: string;
39
+ }
40
+ export interface GenericInstruction extends BaseInstruction {
41
+ type: Exclude<InstructionType, 'FROM' | 'RUN' | 'COPY' | 'ADD' | 'ENV'>;
42
+ arguments: string;
43
+ }
44
+ export type Instruction = FromInstruction | RunInstruction | CopyInstruction | AddInstruction | EnvInstruction | GenericInstruction;
45
+ /** Severity level for optimization recommendations */
46
+ export type Severity = 'HIGH' | 'MEDIUM' | 'LOW';
47
+ /** A single optimization recommendation */
48
+ export interface Recommendation {
49
+ ruleId: string;
50
+ severity: Severity;
51
+ line: number;
52
+ title: string;
53
+ description: string;
54
+ /** Concrete Dockerfile modification suggestion */
55
+ fix: string;
56
+ /** Estimated bytes saved by applying this recommendation */
57
+ estimatedSavingsBytes: number;
58
+ }
59
+ /** Rule interface - each optimization rule implements this */
60
+ export interface Rule {
61
+ id: string;
62
+ name: string;
63
+ description: string;
64
+ evaluate(instructions: Instruction[], context: RuleContext): Recommendation[];
65
+ }
66
+ /** Context passed to rules for size estimation */
67
+ export interface RuleContext {
68
+ /** Base image size lookup function */
69
+ getImageSize: (image: string, tag: string) => number | undefined;
70
+ }
71
+ /** Full analysis result */
72
+ export interface AnalysisResult {
73
+ dockerfilePath: string;
74
+ isMultiStage: boolean;
75
+ baseImage: string;
76
+ instructions: Instruction[];
77
+ recommendations: Recommendation[];
78
+ estimatedBeforeSizeBytes: number;
79
+ estimatedAfterSizeBytes: number;
80
+ sizeReductionPercent: number;
81
+ schemaVersion: number;
82
+ }
package/build/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Typed AST for parsed Dockerfile instructions.
3
+ * Serves as intermediate representation between parser and rules.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@infograb/docker-slim-advisor",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool to analyze Dockerfiles and recommend image size optimizations — reduce build size, catch bloat, improve layer efficiency",
5
+ "type": "module",
6
+ "main": "./build/cli.js",
7
+ "bin": {
8
+ "docker-slim-advisor": "./build/cli.js"
9
+ },
10
+ "files": [
11
+ "build",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "dockerfile",
17
+ "docker",
18
+ "optimization",
19
+ "cli",
20
+ "devops",
21
+ "image-size",
22
+ "container",
23
+ "linter",
24
+ "analyzer",
25
+ "docker-build"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "dev": "tsx src/cli.ts",
35
+ "test": "vitest run",
36
+ "lint": "tsc --noEmit",
37
+ "fetch-sizes": "tsx scripts/fetch-image-sizes.ts",
38
+ "fetch-sizes:fallback": "tsx scripts/fetch-image-sizes.ts --fallback-only",
39
+ "validate-db": "tsx scripts/validate-db.ts",
40
+ "validate:sizes": "tsx scripts/validate-image-sizes.ts"
41
+ },
42
+ "dependencies": {
43
+ "chalk": "^5.3.0",
44
+ "commander": "^12.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^20.14.0",
48
+ "tsx": "^4.16.0",
49
+ "typescript": "^5.5.0",
50
+ "vitest": "^2.0.0"
51
+ }
52
+ }