ai-pdf-builder 0.1.0 → 0.3.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Strykr AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  Professional PDF generation from Markdown using Pandoc and LaTeX. Create beautiful whitepapers, memos, agreements, and term sheets with ease.
4
4
 
5
+ ## Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Prerequisites](#prerequisites)
9
+ - [Installation](#installation)
10
+ - [Quick Start](#quick-start)
11
+ - [Usage Examples](#usage-examples)
12
+ - [Basic PDF Generation](#basic-pdf-generation)
13
+ - [Generate a Business Memo](#generate-a-business-memo)
14
+ - [Generate a Legal Agreement](#generate-a-legal-agreement)
15
+ - [Generate a Term Sheet](#generate-a-term-sheet)
16
+ - [Generate a Whitepaper](#generate-a-whitepaper)
17
+ - [Custom Colors](#custom-colors)
18
+ - [Save to File](#save-to-file)
19
+ - [Custom Templates](#custom-templates)
20
+ - [List Available Templates](#list-available-templates)
21
+ - [Check System Requirements](#check-system-requirements)
22
+ - [Integration with Next.js](#integration-with-nextjs)
23
+ - [API Reference](#api-reference)
24
+ - [Built-in Templates](#built-in-templates)
25
+ - [Troubleshooting](#troubleshooting)
26
+ - [Error Handling](#error-handling)
27
+ - [TypeScript Best Practices](#typescript-best-practices)
28
+ - [Advanced Topics](#advanced-topics)
29
+ - [Production Deployment](#production-deployment)
30
+ - [FAQ](#faq)
31
+ - [License](#license)
32
+ - [Contributing](#contributing)
33
+
5
34
  ## Features
6
35
 
7
36
  - **Multiple Document Types**: Built-in templates for memos, agreements, term sheets, and whitepapers
@@ -498,10 +527,643 @@ const result = await generatePDF({
498
527
  });
499
528
  ```
500
529
 
530
+ ## Error Handling
531
+
532
+ Proper error handling is crucial for production applications.
533
+
534
+ ### Basic Error Handling
535
+
536
+ ```typescript
537
+ import { generatePDF } from 'ai-pdf-builder';
538
+
539
+ try {
540
+ const result = await generatePDF({
541
+ content: markdownContent,
542
+ metadata: { title: 'My Document' }
543
+ });
544
+
545
+ if (!result.success) {
546
+ console.error('PDF generation failed:', result.error);
547
+
548
+ // Handle specific errors
549
+ if (result.error?.includes('Pandoc')) {
550
+ console.error('Pandoc is not installed. Please install it first.');
551
+ // Guide user to installation instructions
552
+ } else if (result.error?.includes('pdflatex')) {
553
+ console.error('LaTeX is not installed. Please install TeX Live or MiKTeX.');
554
+ } else if (result.error?.includes('timeout')) {
555
+ console.error('Generation timed out. Try increasing the timeout option.');
556
+ }
557
+
558
+ return;
559
+ }
560
+
561
+ // Success - use result.buffer or result.path
562
+ console.log(`PDF generated successfully: ${result.fileSize} bytes`);
563
+
564
+ } catch (error) {
565
+ console.error('Unexpected error:', error);
566
+ // Handle system-level errors
567
+ }
568
+ ```
569
+
570
+ ### Retry Logic
571
+
572
+ ```typescript
573
+ async function generateWithRetry(
574
+ options: PDFOptions,
575
+ maxRetries: number = 3
576
+ ): Promise<PDFResult> {
577
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
578
+ const result = await generatePDF(options);
579
+
580
+ if (result.success) {
581
+ return result;
582
+ }
583
+
584
+ console.warn(`Attempt ${attempt}/${maxRetries} failed:`, result.error);
585
+
586
+ // Wait before retry (exponential backoff)
587
+ if (attempt < maxRetries) {
588
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
589
+ }
590
+ }
591
+
592
+ throw new Error(`PDF generation failed after ${maxRetries} attempts`);
593
+ }
594
+ ```
595
+
596
+ ### Error Handling in Express
597
+
598
+ ```typescript
599
+ app.post('/api/pdf', async (req, res) => {
600
+ try {
601
+ const { content, title } = req.body;
602
+
603
+ const result = await generatePDF({
604
+ content,
605
+ metadata: { title },
606
+ timeout: 30000
607
+ });
608
+
609
+ if (!result.success) {
610
+ return res.status(500).json({
611
+ error: 'PDF generation failed',
612
+ details: result.error
613
+ });
614
+ }
615
+
616
+ res.setHeader('Content-Type', 'application/pdf');
617
+ res.setHeader('Content-Disposition', `attachment; filename="${title}.pdf"`);
618
+ res.send(result.buffer);
619
+
620
+ } catch (error) {
621
+ console.error('PDF generation error:', error);
622
+ res.status(500).json({ error: 'Internal server error' });
623
+ }
624
+ });
625
+ ```
626
+
627
+ ## TypeScript Best Practices
628
+
629
+ Get the most out of TypeScript's type system for safer code.
630
+
631
+ ### Fully Typed Configuration
632
+
633
+ ```typescript
634
+ import {
635
+ generatePDF,
636
+ PDFOptions,
637
+ PDFResult,
638
+ DocumentMetadata,
639
+ ColorConfig,
640
+ TemplateConfig
641
+ } from 'ai-pdf-builder';
642
+
643
+ // Type-safe metadata
644
+ const metadata: DocumentMetadata = {
645
+ title: 'Annual Report',
646
+ author: 'Jane Doe',
647
+ date: new Date().toLocaleDateString('en-US'),
648
+ version: 'v2.0',
649
+ subtitle: 'Financial Year 2025'
650
+ };
651
+
652
+ // Type-safe color configuration
653
+ const colors: ColorConfig = {
654
+ primary: '59,130,246', // RGB format
655
+ secondary: '107,114,128',
656
+ accent: '17,24,39'
657
+ };
658
+
659
+ // Complete options with type checking
660
+ const options: PDFOptions = {
661
+ content: markdownContent,
662
+ metadata,
663
+ customColors: colors,
664
+ template: 'default',
665
+ toc: true,
666
+ tocDepth: 2,
667
+ numberSections: true,
668
+ fontSize: 11,
669
+ margin: '1in',
670
+ paperSize: 'letter',
671
+ timeout: 60000
672
+ };
673
+
674
+ const result: PDFResult = await generatePDF(options);
675
+
676
+ // Type-safe result handling
677
+ if (result.success && result.buffer) {
678
+ const size: number = result.fileSize || 0;
679
+ const pages: number = result.pageCount || 0;
680
+ console.log(`Generated ${pages} pages (${size} bytes)`);
681
+ }
682
+ ```
683
+
684
+ ### Custom Type Guards
685
+
686
+ ```typescript
687
+ import { PDFResult } from 'ai-pdf-builder';
688
+
689
+ function isSuccessfulResult(result: PDFResult): result is PDFResult & {
690
+ success: true;
691
+ buffer: Buffer
692
+ } {
693
+ return result.success && !!result.buffer;
694
+ }
695
+
696
+ // Use the type guard
697
+ const result = await generatePDF(options);
698
+ if (isSuccessfulResult(result)) {
699
+ // TypeScript knows result.buffer is defined here
700
+ saveToFile(result.buffer);
701
+ }
702
+ ```
703
+
704
+ ### Extending Types
705
+
706
+ ```typescript
707
+ import { DocumentMetadata } from 'ai-pdf-builder';
708
+
709
+ // Extend with custom metadata
710
+ interface CustomMetadata extends DocumentMetadata {
711
+ department?: string;
712
+ classification?: 'public' | 'internal' | 'confidential';
713
+ reviewers?: string[];
714
+ }
715
+
716
+ const metadata: CustomMetadata = {
717
+ title: 'Security Report',
718
+ author: 'Security Team',
719
+ date: '2026-01-22',
720
+ department: 'IT Security',
721
+ classification: 'confidential',
722
+ reviewers: ['Alice', 'Bob']
723
+ };
724
+ ```
725
+
726
+ ## Advanced Topics
727
+
728
+ ### Batch Processing
729
+
730
+ Generate multiple PDFs concurrently:
731
+
732
+ ```typescript
733
+ import { generatePDF } from 'ai-pdf-builder';
734
+ import pLimit from 'p-limit';
735
+
736
+ async function generateBatch(documents: Array<{ content: string; title: string }>) {
737
+ // Limit concurrent operations to avoid overwhelming the system
738
+ const limit = pLimit(3);
739
+
740
+ const tasks = documents.map(doc =>
741
+ limit(() => generatePDF({
742
+ content: doc.content,
743
+ metadata: { title: doc.title }
744
+ }))
745
+ );
746
+
747
+ const results = await Promise.all(tasks);
748
+
749
+ // Process results
750
+ const successful = results.filter(r => r.success);
751
+ const failed = results.filter(r => !r.success);
752
+
753
+ console.log(`Generated ${successful.length}/${results.length} PDFs`);
754
+ return { successful, failed };
755
+ }
756
+ ```
757
+
758
+ ### Performance Optimization
759
+
760
+ Tips for large documents:
761
+
762
+ ```typescript
763
+ // 1. Increase timeout for large documents
764
+ const result = await generatePDF({
765
+ content: largeMarkdownContent,
766
+ timeout: 180000 // 3 minutes
767
+ });
768
+
769
+ // 2. Disable TOC for faster generation
770
+ const result = await generatePDF({
771
+ content,
772
+ toc: false, // Skip table of contents
773
+ numberSections: false
774
+ });
775
+
776
+ // 3. Use simpler templates
777
+ const result = await generatePDF({
778
+ content,
779
+ template: 'default' // Simpler than termsheet template
780
+ });
781
+
782
+ // 4. Process in chunks for very large documents
783
+ async function generateLargeDocument(sections: string[]) {
784
+ const pdfs = await Promise.all(
785
+ sections.map(section => generatePDF({
786
+ content: section,
787
+ toc: false
788
+ }))
789
+ );
790
+ // Merge PDFs using a library like pdf-lib
791
+ }
792
+ ```
793
+
794
+ ### Memory Management
795
+
796
+ For high-volume operations:
797
+
798
+ ```typescript
799
+ import { generatePDF } from 'ai-pdf-builder';
800
+
801
+ // Monitor memory usage
802
+ function logMemoryUsage() {
803
+ const used = process.memoryUsage();
804
+ console.log({
805
+ rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
806
+ heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`
807
+ });
808
+ }
809
+
810
+ // Process in batches to avoid memory issues
811
+ async function processLargeQueue(queue: any[], batchSize = 10) {
812
+ for (let i = 0; i < queue.length; i += batchSize) {
813
+ const batch = queue.slice(i, i + batchSize);
814
+ await Promise.all(batch.map(item => generatePDF(item)));
815
+
816
+ // Allow garbage collection between batches
817
+ await new Promise(resolve => setImmediate(resolve));
818
+ logMemoryUsage();
819
+ }
820
+ }
821
+ ```
822
+
823
+ ### Custom Template Creation
824
+
825
+ Create your own LaTeX templates:
826
+
827
+ ```typescript
828
+ import { registerTemplate, generatePDF } from 'ai-pdf-builder';
829
+ import * as path from 'path';
830
+
831
+ // Register a custom template
832
+ registerTemplate({
833
+ name: 'company-branded',
834
+ path: path.join(__dirname, 'templates', 'company.latex'),
835
+ description: 'Company branded template with logo',
836
+ supportedDocTypes: ['memo', 'whitepaper', 'report']
837
+ });
838
+
839
+ // Use the custom template
840
+ const result = await generatePDF({
841
+ content: markdownContent,
842
+ template: 'company-branded',
843
+ customColors: {
844
+ primary: '0,102,204', // Company blue
845
+ secondary: '128,128,128'
846
+ }
847
+ });
848
+ ```
849
+
850
+ See [TEMPLATE_GUIDE.md](./TEMPLATE_GUIDE.md) for a complete guide to creating custom LaTeX templates.
851
+
852
+ ## Production Deployment
853
+
854
+ ### Docker Deployment
855
+
856
+ **Dockerfile:**
857
+
858
+ ```dockerfile
859
+ FROM pandoc/latex:latest
860
+
861
+ # Install Node.js
862
+ RUN apk add --no-cache nodejs npm
863
+
864
+ # Set working directory
865
+ WORKDIR /app
866
+
867
+ # Copy package files
868
+ COPY package*.json ./
869
+ RUN npm ci --only=production
870
+
871
+ # Copy application
872
+ COPY . .
873
+
874
+ # Install required LaTeX packages
875
+ RUN tlmgr update --self && \
876
+ tlmgr install \
877
+ collection-fontsrecommended \
878
+ fancyhdr \
879
+ titlesec \
880
+ enumitem \
881
+ xcolor \
882
+ booktabs \
883
+ longtable \
884
+ geometry \
885
+ hyperref \
886
+ setspace \
887
+ array \
888
+ multirow \
889
+ listings
890
+
891
+ EXPOSE 3000
892
+
893
+ CMD ["node", "server.js"]
894
+ ```
895
+
896
+ **docker-compose.yml:**
897
+
898
+ ```yaml
899
+ version: '3.8'
900
+ services:
901
+ pdf-generator:
902
+ build: .
903
+ ports:
904
+ - "3000:3000"
905
+ environment:
906
+ - NODE_ENV=production
907
+ - MAX_CONCURRENT_PDFS=3
908
+ volumes:
909
+ - pdf-cache:/app/cache
910
+ mem_limit: 2g
911
+ mem_reservation: 1g
912
+
913
+ volumes:
914
+ pdf-cache:
915
+ ```
916
+
917
+ ### Next.js Production Configuration
918
+
919
+ ```typescript
920
+ // next.config.js
921
+ module.exports = {
922
+ experimental: {
923
+ serverComponentsExternalPackages: ['ai-pdf-builder']
924
+ },
925
+ // Increase API route timeout for PDF generation
926
+ api: {
927
+ responseLimit: '8mb'
928
+ }
929
+ };
930
+
931
+ // app/api/pdf/route.ts
932
+ import { generatePDF } from 'ai-pdf-builder';
933
+ import { NextResponse } from 'next/server';
934
+
935
+ export const maxDuration = 60; // 60 seconds timeout
936
+
937
+ export async function POST(request: Request) {
938
+ const { content, title } = await request.json();
939
+
940
+ const result = await generatePDF({
941
+ content,
942
+ metadata: { title },
943
+ timeout: 50000 // Leave buffer before Next.js timeout
944
+ });
945
+
946
+ if (!result.success) {
947
+ return NextResponse.json({ error: result.error }, { status: 500 });
948
+ }
949
+
950
+ return new NextResponse(result.buffer, {
951
+ headers: {
952
+ 'Content-Type': 'application/pdf',
953
+ 'Content-Disposition': `attachment; filename="${title}.pdf"`,
954
+ 'Cache-Control': 'no-store'
955
+ }
956
+ });
957
+ }
958
+ ```
959
+
960
+ ### Serverless Considerations
961
+
962
+ **AWS Lambda:**
963
+
964
+ ```typescript
965
+ // Lambda function with custom runtime
966
+ // NOTE: Lambda has a 512MB /tmp limit and 15min timeout
967
+ import { generatePDF } from 'ai-pdf-builder';
968
+
969
+ export const handler = async (event) => {
970
+ // Use /tmp for working directory
971
+ const result = await generatePDF({
972
+ content: event.content,
973
+ metadata: event.metadata,
974
+ workDir: '/tmp/pdf-work',
975
+ timeout: 840000 // 14 minutes (leave buffer)
976
+ });
977
+
978
+ if (result.success) {
979
+ // Upload to S3 instead of returning (size limits)
980
+ await uploadToS3(result.buffer, event.key);
981
+ return { statusCode: 200, body: JSON.stringify({ url: s3Url }) };
982
+ }
983
+
984
+ return { statusCode: 500, body: JSON.stringify({ error: result.error }) };
985
+ };
986
+ ```
987
+
988
+ **Note:** For serverless, consider using the container approach or a dedicated PDF generation service due to Pandoc/LaTeX size requirements.
989
+
990
+ ### Error Monitoring
991
+
992
+ **Sentry Integration:**
993
+
994
+ ```typescript
995
+ import * as Sentry from '@sentry/node';
996
+ import { generatePDF } from 'ai-pdf-builder';
997
+
998
+ Sentry.init({ dsn: process.env.SENTRY_DSN });
999
+
1000
+ async function generateWithMonitoring(options) {
1001
+ const transaction = Sentry.startTransaction({
1002
+ op: 'pdf.generate',
1003
+ name: 'Generate PDF'
1004
+ });
1005
+
1006
+ try {
1007
+ const result = await generatePDF(options);
1008
+
1009
+ if (!result.success) {
1010
+ Sentry.captureMessage('PDF generation failed', {
1011
+ level: 'error',
1012
+ extra: { error: result.error, options }
1013
+ });
1014
+ }
1015
+
1016
+ transaction.setStatus('ok');
1017
+ return result;
1018
+
1019
+ } catch (error) {
1020
+ Sentry.captureException(error);
1021
+ transaction.setStatus('internal_error');
1022
+ throw error;
1023
+ } finally {
1024
+ transaction.finish();
1025
+ }
1026
+ }
1027
+ ```
1028
+
1029
+ ## FAQ
1030
+
1031
+ ### Why is my PDF generation slow?
1032
+
1033
+ PDF generation involves multiple steps (Markdown parsing, LaTeX compilation, font rendering). Typical generation times:
1034
+ - Simple document (1-5 pages): 1-3 seconds
1035
+ - Medium document (10-20 pages): 3-8 seconds
1036
+ - Large document (50+ pages): 10-30 seconds
1037
+
1038
+ **To improve performance:**
1039
+ - Disable TOC if not needed (`toc: false`)
1040
+ - Use simpler templates (`template: 'default'`)
1041
+ - Process documents concurrently for batch operations
1042
+ - Cache generated PDFs when possible
1043
+
1044
+ ### Can I use this in a serverless environment?
1045
+
1046
+ Yes, but with caveats:
1047
+ - **Docker-based serverless** (AWS Fargate, Cloud Run): ✅ Recommended
1048
+ - **Traditional Lambda/Functions**: ⚠️ Challenging due to Pandoc/LaTeX dependencies (~300MB)
1049
+
1050
+ For traditional serverless, consider:
1051
+ 1. Using a custom Lambda layer with Pandoc/LaTeX
1052
+ 2. Calling a dedicated PDF generation service
1053
+ 3. Using AWS Lambda Container Images
1054
+
1055
+ ### How do I debug LaTeX errors?
1056
+
1057
+ ```typescript
1058
+ // Enable detailed error logging
1059
+ const result = await generatePDF({
1060
+ content: markdownContent,
1061
+ metadata: { title: 'Debug Test' }
1062
+ });
1063
+
1064
+ if (!result.success) {
1065
+ console.error('Full error:', result.error);
1066
+ // LaTeX errors typically mention line numbers and commands
1067
+ // Example: "LaTeX Error: Environment Shaded undefined"
1068
+ }
1069
+ ```
1070
+
1071
+ Common LaTeX errors:
1072
+ - **"pdflatex not found"**: Install LaTeX (see Prerequisites)
1073
+ - **"Unicode character not set up"**: Use ASCII alternatives or configure unicode support
1074
+ - **"Environment undefined"**: Missing LaTeX package
1075
+
1076
+ ### What Markdown features are supported?
1077
+
1078
+ Supported (via Pandoc):
1079
+ - ✅ Headers (H1-H6)
1080
+ - ✅ Lists (ordered, unordered, nested)
1081
+ - ✅ Bold, italic, code
1082
+ - ✅ Links and URLs
1083
+ - ✅ Tables (simple and grid)
1084
+ - ✅ Code blocks with syntax highlighting
1085
+ - ✅ Blockquotes
1086
+ - ✅ Horizontal rules
1087
+ - ✅ Images (if file paths are accessible)
1088
+
1089
+ Limited support:
1090
+ - ⚠️ HTML (basic tags only)
1091
+ - ⚠️ Complex tables (may need LaTeX syntax)
1092
+ - ⚠️ Emojis (depends on LaTeX font support)
1093
+
1094
+ Not supported:
1095
+ - ❌ GitHub-flavored Markdown task lists
1096
+ - ❌ Mermaid diagrams (consider rendering to image first)
1097
+
1098
+ ### How do I add images to my PDFs?
1099
+
1100
+ ```typescript
1101
+ const content = `
1102
+ # Document with Images
1103
+
1104
+ ![Company Logo](./assets/logo.png)
1105
+
1106
+ Regular text here.
1107
+
1108
+ ![Chart](./charts/quarterly-results.png){ width=80% }
1109
+ `;
1110
+
1111
+ const result = await generatePDF({
1112
+ content,
1113
+ metadata: { title: 'Document with Images' }
1114
+ });
1115
+ ```
1116
+
1117
+ **Requirements:**
1118
+ - Image paths must be accessible from where the code runs
1119
+ - Supported formats: PNG, JPG, PDF
1120
+ - Use relative or absolute paths
1121
+ - Control size with Pandoc syntax: `{ width=50% }` or `{ height=3in }`
1122
+
1123
+ ### Can I generate PDFs from HTML?
1124
+
1125
+ Yes, convert HTML to Markdown first or use Pandoc's HTML input:
1126
+
1127
+ ```typescript
1128
+ import { generatePDF } from 'ai-pdf-builder';
1129
+ import TurndownService from 'turndown';
1130
+
1131
+ // Convert HTML to Markdown
1132
+ const turndown = new TurndownService();
1133
+ const markdown = turndown.turndown(htmlContent);
1134
+
1135
+ const result = await generatePDF({
1136
+ content: markdown,
1137
+ metadata: { title: 'From HTML' }
1138
+ });
1139
+ ```
1140
+
1141
+ ### How do I handle concurrent requests?
1142
+
1143
+ Use a queue system to limit concurrent PDF generations:
1144
+
1145
+ ```typescript
1146
+ import Queue from 'bull';
1147
+ import { generatePDF } from 'ai-pdf-builder';
1148
+
1149
+ const pdfQueue = new Queue('pdf-generation', process.env.REDIS_URL);
1150
+
1151
+ // Limit to 3 concurrent jobs
1152
+ pdfQueue.process(3, async (job) => {
1153
+ return await generatePDF(job.data.options);
1154
+ });
1155
+
1156
+ // Add jobs
1157
+ app.post('/api/pdf', async (req, res) => {
1158
+ const job = await pdfQueue.add({ options: req.body });
1159
+ res.json({ jobId: job.id });
1160
+ });
1161
+ ```
1162
+
501
1163
  ## License
502
1164
 
503
1165
  MIT
504
1166
 
505
1167
  ## Contributing
506
1168
 
507
- Contributions welcome! Please read our contributing guidelines before submitting PRs.
1169
+ Contributions welcome! Please read our [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines before submitting PRs.