Skip to main content

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.
function greet(name: string) {
  console.log(`Hello, ${name}!`);
}
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.

Remove Comments

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
// This is a helper function
function add(a, b) {
  /* Add two numbers */
  return a + b;
}

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.

Custom Exclusions

You can customize which files and extensions to exclude by creating a repo2pdf.ignore file in the root of your repository.

File Format

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

{
  "ignoredFiles": [
    ".env",
    ".env.local",
    "credentials.json",
    "secrets.yaml"
  ],
  "ignoredExtensions": []
}

Code Formatting

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.")
  );
}
This option only appears when converting remote repositories. Local repositories are never deleted.

Next Steps