Skip to content

Commit 14fd3d8

Browse files
authored
feat: add custom styling and release automation (#7)
- Implement custom CSS injection and URL rewriting to allow users to style PDFs and fix cross-environment links. - Add release-please GitHub Action to automate the release process and enable secure NPM publishing.
1 parent 2c3d67b commit 14fd3d8

7 files changed

Lines changed: 1003 additions & 1199 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: release-please
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
pull-requests: write
11+
id-token: write # Required for Trusted Publishing
12+
13+
jobs:
14+
release-please:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: googleapis/release-please-action@v4
18+
id: release
19+
with:
20+
token: ${{ secrets.GITHUB_TOKEN }}
21+
release-type: node
22+
23+
# The following steps only run after a release PR is merged
24+
- uses: actions/checkout@v4
25+
if: ${{ steps.release.outputs.release_created }}
26+
27+
- name: Setup Node.js
28+
uses: actions/setup-node@v4
29+
if: ${{ steps.release.outputs.release_created }}
30+
with:
31+
node-version: '20'
32+
registry-url: 'https://registry.npmjs.org'
33+
34+
- name: Install dependencies
35+
if: ${{ steps.release.outputs.release_created }}
36+
run: yarn install
37+
38+
- name: Build project
39+
if: ${{ steps.release.outputs.release_created }}
40+
run: yarn build
41+
42+
- name: Publish to NPM (Trusted Publishing)
43+
if: ${{ steps.release.outputs.release_created }}
44+
run: npm publish --provenance --access public
45+
# NODE_AUTH_TOKEN is NO LONGER NEEDED with Trusted Publishing

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Dependency directories
2+
node_modules/
3+
4+
# Build outputs
5+
dist/
6+
*.pdf
7+
8+
# IDE and OS files
9+
.DS_Store
10+
.idea/
11+
.vscode/
12+
13+
# Package manager lockfiles (if multiple exist, usually only one is kept)
14+
# Keeping yarn.lock as it was already in the repo, ignoring others if they are temporary
15+
package-lock.json
16+
17+
# Environment variables
18+
.env
19+
.env.local

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ You can find a sample PDF generated by this tool at [doccusaurus.pdf](./example/
1313
* **Full Site PDF Export**: Exports all documentation pages from a Docusaurus site into a single, cohesive PDF file.
1414
* **Concurrent Processing**: Leverages Puppeteer's concurrency capabilities to speed up the page content fetching process.
1515
* **Custom Cover Page**: Supports adding a custom PDF cover page using either a URL or a local file path.
16-
* **Automatic Table of Contents (TOC) Generation**: Automatically generates a clickable PDF table of contents based on the Docusaurus sidebar structure.
16+
* **Automatic Table of Contents (TOC) Generation**: Automatically generates a professional, clickable PDF table of contents with dot leaders based on the Docusaurus sidebar structure.
1717
* **Navigable Internal Links**: Rewrites all internal links within the documentation so they remain clickable and navigable in a PDF reader.
18+
* **Custom CSS Injection**: Supports injecting custom CSS styles directly into the generated PDF for fine-tuned layout control.
19+
* **Link Rewriting**: Allows rewriting site domains/URLs within hyperlinks to ensure cross-references work correctly across environments.
1820
* **Configurable Margins**: Supports adjusting PDF page margins via command-line arguments.
1921

2022
## Installation
@@ -39,10 +41,17 @@ npx docusaurus-docs-to-pdf -h
3941

4042
### Examples
4143

42-
**Generate pdf from Docusaurus website**
44+
**Generate PDF with custom margins and concurrency**
4345

4446
```bash
45-
docusaurus-docs-to-pdf --docs-url https://docusaurus.io/docs --pdf-path docusaurus.pdf --pdf-cover-image https://docusaurus.io/img/docusaurus_keytar.svg
46-
# or
47-
npx docusaurus-docs-to-pdf --docs-url https://docusaurus.io/docs --pdf-path docusaurus.pdf --pdf-cover-image https://docusaurus.io/img/docusaurus_keytar.svg
48-
```
47+
docusaurus-docs-to-pdf --docs-url http://localhost:3000/docs --pdf-path local-docs.pdf --pdf-margin-mm 20 --page-concurrency 10
48+
```
49+
50+
**Advanced: Inject custom CSS and rewrite links**
51+
52+
```bash
53+
docusaurus-docs-to-pdf --docs-url http://localhost:3000/docs \
54+
--pdf-path docs.pdf \
55+
--css "h1 { color: #2e8555; }" \
56+
--site-rewrite "http://localhost:3000=https://docs.example.com"
57+
```

src/docusaurus.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,6 @@ export async function getElementOuterHtml(selector: string): Promise<string> {
176176
const element: HTMLElement | null = document.querySelector(selector);
177177

178178
if (element) {
179-
// Apply CSS style to force a page break *after* this element in the generated PDF.
180-
// This is crucial for ensuring each extracted document content block starts on a new page.
181-
element.style.pageBreakAfter = 'always';
182-
183179
// Return the outer HTML (including the element itself and its content).
184180
return element.outerHTML;
185181
} else {
@@ -198,33 +194,57 @@ export async function getElementOuterHtml(selector: string): Promise<string> {
198194
* as it converts original relative/absolute URLs (e.g., `/docs/my-page`) into
199195
* anchor links (e.g., `#my-page-id`) that point to elements within the merged document.
200196
*
197+
* It also supports rewriting specific site domains/URLs to new ones, which is useful
198+
* for redirecting links to production environments while fetching content from development/local ones.
199+
*
201200
* @param pathToAnchors An array of tuples, where each tuple contains:
202201
* - `string`: The original path or href of a link (e.g., '/docs/introduction').
203202
* - `string`: The corresponding target anchor ID within the merged document
204203
* (e.g., 'id-timestamp-random').
204+
* @param siteRewrites Optional. An array of [from, to] tuples for domain/URL rewriting.
205205
* @returns {Promise<void>} A Promise that resolves when all matching links have been rewritten.
206206
*/
207-
export async function rewriteLinks(pathToAnchors: [string, string][]): Promise<void> {
207+
export async function rewriteLinks(pathToAnchors: [string, string][], siteRewrites?: [string, string][]): Promise<void> {
208208
// Select all <a> elements that have an 'href' attribute.
209209
// Cast to HTMLAnchorElement for proper TypeScript type inference and access to link-specific properties.
210210
const allLinks = document.querySelectorAll('a[href]');
211-
211+
212212
for (let i = 0; i < allLinks.length; i++) {
213213
const element = allLinks[i] as HTMLAnchorElement;
214214
// Get the value of the 'href' attribute.
215-
let linkPath = element.getAttribute('href') || '';
216-
215+
let href = element.getAttribute('href') || '';
216+
217+
// Priority 1: Internal Documentation Anchor Rewriting
217218
// Iterate through the provided map of original paths to new anchor IDs.
219+
let matchedInternal = false;
218220
for (let j = 0; j < pathToAnchors.length; j++) {
219221
const [path, anchor] = pathToAnchors[j];
220222
// If the current link's href matches an original path in the map,
221223
// rewrite its href to point to the new anchor ID.
222-
if (linkPath === path) {
224+
if (href === path) {
223225
element.setAttribute('href', '#' + anchor);
226+
matchedInternal = true;
224227
// Break inner loop as we found a match for this link
225228
break;
226229
}
227230
}
231+
232+
// Skip site rewrite if it's already an internal documentation link
233+
if (matchedInternal) {
234+
continue;
235+
}
236+
237+
// Priority 2: External/Site Domain Rewriting
238+
// If site rewrite rules are provided, check if the href matches any rule.
239+
if (siteRewrites && siteRewrites.length > 0) {
240+
for (const [from, to] of siteRewrites) {
241+
if (href.includes(from)) {
242+
element.setAttribute('href', href.replace(from, to));
243+
// Break after the first matching rewrite rule
244+
break;
245+
}
246+
}
247+
}
228248
}
229249
}
230250

0 commit comments

Comments
 (0)