zod->yargs

This commit is contained in:
lovebird 2025-04-23 20:01:29 +02:00
parent 0762a888fd
commit 8e0723ce9f
40 changed files with 1406119 additions and 158 deletions

View File

@ -5,42 +5,6 @@ import * as z from 'zod';
import { runConversion } from '../lib/convert.js';
export const command = 'convert';
export const desc = 'Convert PDF to images';
export const builder = {
input: {
alias: 'i',
type: 'string',
description: 'Input PDF file',
demandOption: true
},
output: {
alias: 'o',
type: 'string',
description: 'Output path pattern or directory. Variables like ${SRC_DIR}, ${PAGE} etc. are supported. Uses a default pattern if omitted.',
},
dpi: {
type: 'number',
description: 'DPI for output images',
default: 300
},
format: {
type: 'string',
choices: ['png', 'jpg'],
default: 'png',
description: 'Output image format'
},
startPage: {
alias: 's',
type: 'number',
description: 'First page to convert (1-based)',
requiresArg: true
},
endPage: {
alias: 'e',
type: 'number',
description: 'Last page to convert (1-based, inclusive)',
requiresArg: true
}
};
export async function handler(argv) {
const logger = new Logger();
try {
@ -49,7 +13,7 @@ export async function handler(argv) {
throw new Error(`Input file ${config.input} does not exist`);
}
logger.info("Calling conversion library function...");
const outputFiles = await runConversion(config);
const outputFiles = await runConversion(config, logger);
logger.info(`Conversion completed successfully`);
logger.info(`Generated ${outputFiles.length} images`);
}

View File

@ -1,10 +1,12 @@
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import * as convertCommand from './commands/convert.js';
import { ConvertCommandArgsSchema } from './types.js';
import { toYargs } from '@polymech/commons';
const commandModule = {
command: convertCommand.command,
describe: convertCommand.desc,
builder: convertCommand.builder,
builder: (yargs) => toYargs(yargs, ConvertCommandArgsSchema),
handler: convertCommand.handler
};
yargs(hideBin(process.argv))

View File

@ -1,18 +1,17 @@
import { Logger } from "tslog";
import { statSync } from "node:fs";
import { sep, resolve as pathResolve, parse as pathParse, relative as pathRelative } from "node:path";
import { readFile } from "node:fs/promises";
import { DEFAULT_ROOTS, DEFAULT_VARS, pathInfoEx } from "@polymech/commons";
import { convertPdfToImages } from "./pdf.js"; // Import the actual PDF conversion function
import { DEFAULT_OUTPUT_TEMPLATE } from "../constants.js"; // Import the constant
import { convertPdfToImages } from "./pdf.js";
import { DEFAULT_OUTPUT_TEMPLATE } from "../constants.js";
/**
* Runs the PDF to images conversion process.
* Generates variables, determines output path, reads PDF, and calls the conversion engine.
* @param config - The conversion configuration options.
* @param config - The conversion configuration options (inferred from Zod schema).
* @param logger - The logger instance to use for logging.
* @returns A promise that resolves with an array of generated image file paths.
*/
export async function runConversion(config) {
const logger = config.logger || new Logger();
export async function runConversion(config, logger) {
const inputPath = pathResolve(config.input);
let srcInfo = {};
try {

View File

@ -1,25 +1,33 @@
import { z } from 'zod';
export const ConvertCommandSchema = z.object({
// Define the base shape for arguments
export const ConvertCommandArgsSchema = z.object({
input: z.string(),
output: z.string().optional(),
dpi: z.number().int().positive().default(300),
format: z.enum(['png', 'jpg']).default('png'),
startPage: z.number().int().positive().optional(),
endPage: z.number().int().positive().optional(),
i: z.string().optional(),
o: z.string().optional(),
s: z.number().int().positive().optional(),
e: z.number().int().positive().optional(),
_: z.array(z.union([z.string(), z.number()])).optional(),
$0: z.string().optional()
}).transform((data) => ({
input: data.input ?? data.i,
output: data.output ?? data.o,
dpi: data.dpi,
format: data.format,
startPage: data.startPage ?? data.s,
endPage: data.endPage ?? data.e
})).refine((data) => {
endPage: z.number().int().positive().optional()
});
// Add refinements, transformations, and catchall for final validation/parsing
export const ConvertCommandSchema = ConvertCommandArgsSchema
.catchall(z.any()) // Allow var-* and other properties
.transform((data) => {
// Explicitly pick known fields + extras (var-*)
const known = {
input: data.input,
output: data.output,
dpi: data.dpi,
format: data.format,
startPage: data.startPage,
endPage: data.endPage,
};
// Keep only extra properties (like var-*)
const extras = Object.keys(data)
.filter(key => !['input', 'output', 'dpi', 'format', 'startPage', 'endPage', '_', '$0'].includes(key))
.reduce((acc, key) => { acc[key] = data[key]; return acc; }, {});
return { ...known, ...extras };
})
.refine((data) => {
if (data.startPage !== undefined && data.endPage !== undefined) {
return data.startPage <= data.endPage;
}

View File

@ -46,8 +46,8 @@
"tslog": "^3.3.3",
"tsup": "^2.0.3",
"yargs": "^17.7.2",
"zod": "^3.24.2",
"zod-to-json-schema": "^3.24.1",
"zod": "^3.24.3",
"zod-to-json-schema": "^3.24.5",
"zod-to-ts": "^1.2.0"
},
"devDependencies": {

View File

@ -10,7 +10,7 @@
"dev": "tsc -p . --watch",
"build": "tsc",
"start": "node dist/index.js",
"test:pdf": "node dist/index.js convert -i tests/e5dc.pdf -o tests/out/e5dc/ --startPage 3 --endPage 5",
"test:pdf": "node dist/index.js convert --input tests/e5dc.pdf -o tests/out/e5dc/ --startPage 3 --endPage 5",
"test:basic": "vitest run",
"test:variables": "vitest run tests/cli/variables.test.ts"
},

View File

@ -2,64 +2,21 @@ import { Arguments } from 'yargs';
import { Logger } from 'tslog';
import { ConvertCommandSchema, ConvertCommandConfig } from '../types.js';
import { existsSync } from 'node:fs';
import { resolve as pathResolve } from 'node:path';
import * as z from 'zod';
import type { Options } from 'yargs';
import { runConversion, IRunConversionOptions } from '../lib/convert.js';
import { runConversion } from '../lib/convert.js';
export const command = 'convert';
export const desc = 'Convert PDF to images';
export const builder: { [key: string]: Options } = {
input: {
alias: 'i',
type: 'string',
description: 'Input PDF file',
demandOption: true
},
output: {
alias: 'o',
type: 'string',
description: 'Output path pattern or directory. Variables like ${SRC_DIR}, ${PAGE} etc. are supported. Uses a default pattern if omitted.',
},
dpi: {
type: 'number',
description: 'DPI for output images',
default: 300
},
format: {
type: 'string',
choices: ['png', 'jpg'] as const,
default: 'png',
description: 'Output image format'
},
startPage: {
alias: 's',
type: 'number',
description: 'First page to convert (1-based)',
requiresArg: true
},
endPage: {
alias: 'e',
type: 'number',
description: 'Last page to convert (1-based, inclusive)',
requiresArg: true
}
};
export async function handler(argv: Arguments<ConvertCommandConfig>): Promise<void> {
const logger = new Logger();
const logger = new Logger();
try {
const config = ConvertCommandSchema.parse(argv);
const config = ConvertCommandSchema.parse(argv);
if (!existsSync(config.input)) {
throw new Error(`Input file ${config.input} does not exist`);
}
logger.info("Calling conversion library function...");
const outputFiles = await runConversion(config as IRunConversionOptions);
const outputFiles = await runConversion(config, logger);
logger.info(`Conversion completed successfully`);
logger.info(`Generated ${outputFiles.length} images`);
} catch (error) {

View File

@ -2,12 +2,13 @@ import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import * as convertCommand from './commands/convert.js';
import type { CommandModule } from 'yargs';
import type { ConvertCommandConfig } from './types.js';
import { ConvertCommandConfig, ConvertCommandArgsSchema } from './types.js';
import { toYargs } from '@polymech/commons';
const commandModule: CommandModule<{}, ConvertCommandConfig> = {
command: convertCommand.command,
describe: convertCommand.desc,
builder: convertCommand.builder,
builder: (yargs) => toYargs(yargs, ConvertCommandArgsSchema),
handler: convertCommand.handler
};

View File

@ -1,33 +1,20 @@
import { Logger } from "tslog";
import { statSync } from "node:fs";
import { sep, resolve as pathResolve, parse as pathParse, relative as pathRelative } from "node:path";
import { readFile } from "node:fs/promises";
import { DEFAULT_ROOTS, DEFAULT_VARS, pathInfoEx } from "@polymech/commons";
import { convertPdfToImages } from "./pdf.js"; // Import the actual PDF conversion function
import { DEFAULT_OUTPUT_TEMPLATE } from "../constants.js"; // Import the constant
// Define an interface for the configuration options needed by the library function
// This might be similar to SimpleOptions or ConvertCommandConfig, but tailored for the library
export interface IRunConversionOptions {
input: string;
output?: string;
dpi: number;
format: "png" | "jpg";
startPage?: number;
endPage?: number;
logger?: Logger<any>;
[key: string]: any; // Allow other properties like var-*
}
import { readFile } from "node:fs/promises";
import { DEFAULT_ROOTS, DEFAULT_VARS, pathInfoEx, resolveVariables } from "@polymech/commons";
import { convertPdfToImages } from "./pdf.js";
import { DEFAULT_OUTPUT_TEMPLATE } from "../constants.js";
import type { ConvertCommandConfig } from "../types.js";
/**
* Runs the PDF to images conversion process.
* Generates variables, determines output path, reads PDF, and calls the conversion engine.
* @param config - The conversion configuration options.
* @param config - The conversion configuration options (inferred from Zod schema).
* @param logger - The logger instance to use for logging.
* @returns A promise that resolves with an array of generated image file paths.
*/
export async function runConversion(config: IRunConversionOptions): Promise<string[]> {
const logger = config.logger || new Logger<any>();
export async function runConversion(config: ConvertCommandConfig, logger: Logger<any>): Promise<string[]> {
const inputPath = pathResolve(config.input);
let srcInfo: any = {};
try {

View File

@ -1,35 +1,44 @@
import { z } from 'zod';
import type { ImageFormat } from './lib/pdf.js';
export const ConvertCommandSchema = z.object({
// Define the base shape for arguments
export const ConvertCommandArgsSchema = z.object({
input: z.string(),
output: z.string().optional(),
dpi: z.number().int().positive().default(300),
format: z.enum(['png', 'jpg']).default('png'),
startPage: z.number().int().positive().optional(),
endPage: z.number().int().positive().optional(),
i: z.string().optional(),
o: z.string().optional(),
s: z.number().int().positive().optional(),
e: z.number().int().positive().optional(),
_: z.array(z.union([z.string(), z.number()])).optional(),
$0: z.string().optional()
}).transform((data) => ({
input: data.input ?? data.i,
output: data.output ?? data.o,
dpi: data.dpi,
format: data.format,
startPage: data.startPage ?? data.s,
endPage: data.endPage ?? data.e
})).refine((data) => {
if (data.startPage !== undefined && data.endPage !== undefined) {
return data.startPage <= data.endPage;
}
return true;
}, {
message: "startPage must be less than or equal to endPage",
path: ["startPage"],
endPage: z.number().int().positive().optional()
});
// Add refinements, transformations, and catchall for final validation/parsing
export const ConvertCommandSchema = ConvertCommandArgsSchema
.catchall(z.any()) // Allow var-* and other properties
.transform((data) => {
// Explicitly pick known fields + extras (var-*)
const known = {
input: data.input,
output: data.output,
dpi: data.dpi,
format: data.format,
startPage: data.startPage,
endPage: data.endPage,
};
// Keep only extra properties (like var-*)
const extras = Object.keys(data)
.filter(key => !['input', 'output', 'dpi', 'format', 'startPage', 'endPage', '_', '$0'].includes(key))
.reduce((acc, key) => { acc[key] = data[key]; return acc; }, {} as any);
return { ...known, ...extras };
})
.refine((data) => {
if (data.startPage !== undefined && data.endPage !== undefined) {
return data.startPage <= data.endPage;
}
return true;
}, {
message: "startPage must be less than or equal to endPage",
path: ["startPage"],
});
export type ConvertCommandConfig = z.infer<typeof ConvertCommandSchema>;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ describe('CLI Integration Test - Variable Output Path', () => {
// Ensure paths in the command are relative to the execution directory if needed,
// but here inputPdf is relative, and outputPattern relies on internal resolution.
// Quote the output pattern for safety in the shell.
const command = `node dist/index.js convert -i "${inputPdf}" -o "${outputPattern}"`;
const command = `node dist/index.js convert --input "${inputPdf}" --output "${outputPattern}"`;
// Execute the command
let commandOutput = '';