Interactive Configuration
repo2pdf uses an interactive prompt system powered by Inquirer.js to guide you through the configuration process. All options are configured at runtime—there’s no config file to maintain.
Available Features
When you run repo2pdf, you’ll be presented with a checkbox prompt to select features:
? Select the features you want to include: (Press <space> to select, <a> to toggle all, <i> to invert)
❯ ◯ Add line numbers
◯ Add highlighting
◯ Add page numbers
◯ Remove comments
◯ Remove empty lines
◯ One PDF per file
Use the spacebar to select or deselect each feature.
Add Line Numbers
Adds line numbers to the left side of each line of code in the PDF output.
Without Line Numbers
With Line Numbers
function greet(name: string) {
console.log(`Hello, ${name}!`);
}
1 function greet(name: string) {
2 console.log(`Hello, ${name}!`);
3 }
Implementation Details:
The line numbers are automatically padded to align properly based on the total number of lines:
const lineNumWidth = hlData
.filter((d) => d.text === "\n")
.length.toString().length;
// Later in the code:
doc.text(
String(lineNum++).padStart(lineNumWidth, " ") + " ",
{ continued: true, textIndent: 0 }
);
Add Highlighting
Applies syntax highlighting to code using highlight.js. The tool automatically detects the language based on file extensions.
Supported Languages:
Highlight.js supports 190+ languages. The tool attempts to detect the language from the file extension:
if (addHighlighting && hljs.getLanguage(extension)) {
highlightedCode = hljs.highlight(data, {
language: extension,
}).value;
} else {
highlightedCode = hljs.highlight(data, {
language: "plaintext",
}).value;
}
If the language can’t be detected, it falls back to plaintext rendering.
Syntax highlighting adds color to different code elements like keywords, strings, comments, and functions, making the PDF more readable.
Add Page Numbers
Adds page numbers in the format “Page: X of Y” centered at the bottom of each page.
if (addPageNumbers) {
const oldBottomMargin = doc.page.margins.bottom;
doc.page.margins.bottom = 0;
doc.text(
`Page: ${i + 1} of ${pages.count}`,
0,
doc.page.height - oldBottomMargin / 2,
{ align: "center" }
);
doc.page.margins.bottom = oldBottomMargin;
}
Page numbers are only added when using the single PDF output mode. They are not included when using “One PDF per file” mode.
Strips comments from source code using the strip-comments library.
if (removeComments) {
data = strip(data);
}
This removes:
- Single-line comments (
//, #, etc.)
- Multi-line comments (
/* */, <!-- -->, etc.)
- Docstring comments
Remove Empty Lines
Removes blank lines from the output to create more compact PDFs.
if (removeEmptyLines) {
data = data.replace(/^\s*[\r\n]/gm, "");
}
This feature is useful for reducing PDF size and page count, especially for codebases with heavy whitespace formatting.
One PDF per File
Generates a separate PDF for each file instead of combining everything into a single PDF.
When this option is selected:
- You’ll be prompted for an output folder name instead of a file name
- Each file gets its own PDF named after its path
- The default folder is
./output
? Please provide an output folder name: (./output)
File Naming Convention:
PDFs are named using the file’s relative path with path separators replaced by underscores:
const pdfFileName = path
.join(outputFolderName, fileName.replace(path.sep, "_"))
.concat(".pdf");
Example:
src/components/Button.tsx → ./output/src_components_Button.tsx.pdf
README.md → ./output/README.md.pdf
See Output Options for more details.
Output Configuration
The output configuration depends on whether you select “One PDF per file”:
You’ll be prompted for a filename:? Please provide an output file name: (output.pdf)
Default: output.pdfThis creates one PDF containing all files in the repository. You’ll be prompted for a folder name:? Please provide an output folder name: (./output)
Default: ./outputThis creates a folder containing individual PDFs for each file.
Custom Exclusions
You can customize which files and extensions to exclude by creating a repo2pdf.ignore file in the root of your repository.
The repo2pdf.ignore file uses JSON format:
{
"ignoredFiles": [
"secrets.txt",
".env",
"config.local.js"
],
"ignoredExtensions": [
".log",
".tmp",
".cache"
]
}
How It Works
The tool loads the ignore configuration using:
export interface IgnoreConfig {
ignoredFiles: string[];
ignoredExtensions: string[];
}
export default async function loadIgnoreConfig(
rootDir: string,
): Promise<IgnoreConfig | null> {
const ignoreConfigPath = path.join(rootDir, "repo2pdf.ignore");
try {
const data = await fs.promises.readFile(ignoreConfigPath, "utf8");
const config = JSON.parse(data) as IgnoreConfig;
return config;
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
return null; // File doesn't exist, use defaults
}
throw err;
}
}
Custom exclusions are merged with the universal exclusions:
const excludedNames = universalExcludedNames;
const excludedExtensions = universalExcludedExtensions;
if (ignoreConfig?.ignoredFiles)
excludedNames.push(...ignoreConfig.ignoredFiles);
if (ignoreConfig?.ignoredExtensions)
excludedExtensions.push(...ignoreConfig.ignoredExtensions);
Universal Exclusions
These files and extensions are always excluded:
Files/Directories:
const universalExcludedNames = [
".gitignore",
".gitmodules",
"package-lock.json",
"yarn.lock",
".git",
"repo2pdf.ignore",
".vscode",
".idea",
".vs",
"node_modules",
];
Extensions:
const universalExcludedExtensions = [
".png", ".jpg", ".jpeg", ".gif", ".svg", ".bmp", ".webp", ".ico",
".mp4", ".mov", ".avi", ".wmv",
".yml",
".pdf",
];
The repo2pdf.ignore file itself is always excluded from the PDF output.
Example Use Cases
Exclude Sensitive Files
Exclude Build Artifacts
Exclude Test Files
{
"ignoredFiles": [
".env",
".env.local",
"credentials.json",
"secrets.yaml"
],
"ignoredExtensions": []
}
{
"ignoredFiles": [
"dist",
"build",
".next",
"out"
],
"ignoredExtensions": [
".map",
".min.js",
".min.css"
]
}
{
"ignoredFiles": [
"__tests__",
"__mocks__",
"coverage"
],
"ignoredExtensions": [
".test.js",
".spec.ts",
".test.tsx"
]
}
repo2pdf automatically formats code using Prettier when a supported parser is available.
Supported File Types
const parserOptions: { [key: string]: string } = {
js: "babel", jsx: "babel", ts: "typescript", tsx: "typescript",
css: "css", scss: "scss", less: "less",
html: "html", json: "json", md: "markdown",
yaml: "yaml", graphql: "graphql",
vue: "vue", angular: "angular", xml: "xml",
java: "java", kotlin: "kotlin", swift: "swift",
php: "php", ruby: "ruby", python: "python",
perl: "perl", shell: "sh", dockerfile: "dockerfile", ini: "ini",
};
If Prettier formatting fails, the tool falls back to plain text:
if (parser) {
try {
data = await prettier.format(data, { parser });
} catch (error: unknown) {
const errorMessage = (error as Error).message.split("\n")[0];
console.warn(
`Plain text fallback at ${filePath}: ${errorMessage}`,
);
}
}
Repository Cleanup
For remote repositories, you can choose whether to keep the cloned repository:
? Do you want to keep the cloned repository? (Use arrow keys)
❯ No
Yes
The tempRepo directory is automatically deleted after a 3-second delay:if (!keepRepo && !useLocalRepo) {
await delay(3000);
fs.rmSync(tempDir, { recursive: true, force: true });
spinner.succeed(
chalk.greenBright("Temporary repository has been deleted.")
);
}
The repository remains in the tempRepo directory for inspection or reuse.
This option only appears when converting remote repositories. Local repositories are never deleted.
Next Steps