init CV repo
This commit is contained in:
commit
432e6c0025
|
|
@ -0,0 +1,15 @@
|
|||
node_modules/
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Generated HTML can be regenerated, but keep PDFs in version control
|
||||
output/html/
|
||||
|
||||
# Editor / OS
|
||||
.DS_Store
|
||||
*.swp
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Puppeteer download cache
|
||||
.cache/
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
# CV-2026 — Project Instructions for Claude
|
||||
|
||||
This project is Oleg's CV generation pipeline. It supports three workflows:
|
||||
|
||||
1. **Base CV refresh** — turn a new Markdown CV into a styled PDF.
|
||||
2. **Company tailoring** — quickly adapt the latest base CV to a specific company/role and produce a tuned PDF.
|
||||
3. **Application tracking** — log applications, outreaches, and follow-up communications.
|
||||
|
||||
The visual format of every generated PDF must match `base/reference/Oleg_Proskurin_Techlead_resume.docx.pdf` — that file is the canonical style reference.
|
||||
|
||||
---
|
||||
|
||||
## Folder layout
|
||||
|
||||
```
|
||||
base/
|
||||
cv-YYYY-MM-base.md # current master CV in Markdown
|
||||
reference/
|
||||
Oleg_Proskurin_*.pdf # canonical visual reference (do not edit)
|
||||
templates/
|
||||
cv-style.css # shared stylesheet — used by every CV
|
||||
cv-template.html # skeleton HTML (use as a starting point)
|
||||
tailored/
|
||||
<company-slug>/
|
||||
cv.md # tailored Markdown
|
||||
cv.html # generated HTML
|
||||
cv.pdf # final PDF
|
||||
notes.md # job posting, contact, decisions log
|
||||
tracking/
|
||||
applications.md # one row per application
|
||||
outreach.md # cold outreach + recruiter conversations
|
||||
scripts/
|
||||
generate-pdf.mjs # HTML -> PDF (puppeteer)
|
||||
output/
|
||||
html/ # generated HTML for base CVs (transient)
|
||||
pdf/ # final PDFs for base CVs (committed)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow 1 — Generate base CV PDF from Markdown
|
||||
|
||||
**Input**: `base/cv-YYYY-MM-base.md`
|
||||
**Output**: `output/pdf/cv-YYYY-MM-base.pdf`
|
||||
|
||||
Steps:
|
||||
|
||||
1. Read the Markdown source and the reference PDF (`base/reference/...`) to confirm structure.
|
||||
2. Generate `output/html/cv-YYYY-MM-base.html` using `templates/cv-template.html` as a starting point.
|
||||
- Link the stylesheet via `<link rel="stylesheet" href="/templates/cv-style.css" />` (absolute path — the PDF script serves the project root).
|
||||
- Map MD content to HTML tags **verbatim** — section titles, punctuation, list structure, dates, and contact separators must match MD character-for-character. The structure below is the tag shape; the **text** comes from MD. See "Content vs. style — separation rule" below.
|
||||
3. Run the PDF generator:
|
||||
```bash
|
||||
pnpm pdf output/html/cv-YYYY-MM-base.html
|
||||
```
|
||||
4. Open the resulting PDF (`output/pdf/cv-YYYY-MM-base.pdf`) and verify single-page-ish, no orphan bullets, no overflow.
|
||||
|
||||
---
|
||||
|
||||
## Workflow 2 — Tailor CV for a specific company
|
||||
|
||||
**Input**: latest `base/cv-*.md` + job description / company info
|
||||
**Output**: `tailored/<company-slug>/cv.pdf`
|
||||
|
||||
Steps:
|
||||
|
||||
1. Create `tailored/<company-slug>/` folder. Use a short kebab-case slug (e.g. `vercel`, `stripe-platform`, `anthropic-applied`).
|
||||
2. Copy the base MD into `tailored/<company-slug>/cv.md`.
|
||||
3. Tailor the content:
|
||||
- **Summary**: re-frame around the company's stack and the role's keywords. Keep facts intact, shift emphasis.
|
||||
- **Skills**: reorder so the most relevant categories/tools appear first. Drop categories that are irrelevant to keep density.
|
||||
- **Bullets**: reorder within each job so the top bullets match what the company values. Edit phrasing to use the company's vocabulary, but never invent achievements.
|
||||
- Keep length ≤ 2 pages.
|
||||
4. Save the job posting, recruiter contacts, and tailoring decisions in `tailored/<company-slug>/notes.md`.
|
||||
5. Generate `tailored/<company-slug>/cv.html` (same template/CSS).
|
||||
6. Run the PDF generator with an explicit output directory:
|
||||
```bash
|
||||
pnpm pdf tailored/<company-slug>/cv.html tailored/<company-slug>
|
||||
```
|
||||
That writes `tailored/<company-slug>/cv.pdf`.
|
||||
7. Add a row to `tracking/applications.md`.
|
||||
|
||||
---
|
||||
|
||||
## Workflow 3 — Tracking
|
||||
|
||||
- **`tracking/applications.md`**: one row per application. Status column drives the funnel (applied, screen, interview, offer, rejected, withdrawn). Always store absolute dates (e.g. `2026-05-24`).
|
||||
- **`tracking/outreach.md`**: log cold messages, recruiter pings, and follow-ups. One row per touchpoint; group by company.
|
||||
- When asked to "log this application" or similar, append to the right file — never rewrite existing rows unless explicitly asked.
|
||||
|
||||
---
|
||||
|
||||
## Content vs. style — separation rule
|
||||
|
||||
**This is the load-bearing rule for MD→HTML conversion. Read it before generating any HTML.**
|
||||
|
||||
All text in the generated HTML comes **verbatim** from the source Markdown — section titles, contact-row separators, date formats, punctuation, bullet leads, list structure. The reference PDF supplies **only** visual conventions: typography, sizes, margins, colors, spacing, alignment, two-column header rows. The only allowed transformation is mapping MD markup to HTML tags:
|
||||
|
||||
- `# x` → `<h1>x</h1>`
|
||||
- `## x` → `<h2>x</h2>` (exact text — do not rename)
|
||||
- `### x` → article header (exact text — do not reword)
|
||||
- `**x**` → `<strong>x</strong>`
|
||||
- `*x*` / `_x_` → `<em>x</em>`
|
||||
- `` `x` `` → `<code>x</code>`
|
||||
- `[text](url)` → `<a href="url">text</a>`
|
||||
- `- item` → `<li>item</li>`
|
||||
- `---` (horizontal rule) → drop (section separators are visual via `<h2>` underline)
|
||||
|
||||
Forbidden rewrites (examples — do not do these):
|
||||
|
||||
- `## Summary` → `Professional Summary` (rename) ❌
|
||||
- `## Experience` → `Work History` (rename) ❌
|
||||
- `·` separators in contacts → `|` (substitution) ❌
|
||||
- `Feb 2025 – Present` → `02/2025 — Current` (date reformatting) ❌
|
||||
- Skills bullet list collapsed into a dense paragraph (structure change) ❌
|
||||
- Job-description paragraph truncated to a short label (content loss) ❌
|
||||
|
||||
If the visual style of the reference PDF appears to conflict with what's in MD, the **MD wins**. Update MD if you want different content; never silently rewrite it during HTML generation.
|
||||
|
||||
---
|
||||
|
||||
## Visual format rules (must match the reference PDF)
|
||||
|
||||
The CSS in `templates/cv-style.css` already encodes these — when generating HTML, just use the classes and the format will follow. Reference for human review:
|
||||
|
||||
- **Page**: A4, ~12mm margins, single column.
|
||||
- **Font**: Times New Roman (serif), body 10.5pt, line-height 1.25.
|
||||
- **Header**: centered name in 14pt bold caps, role in 11pt bold, single contact row with `|` separators.
|
||||
- **Section headings**: ALL CAPS, bold, 10.5pt, with a thin bottom rule.
|
||||
- **Job entry**:
|
||||
- Header row: company (bold, left) — role (bold, right).
|
||||
- Meta row: short company descriptor (italic, left) — dates (italic, right).
|
||||
- Bulleted list with disc markers, hanging indent.
|
||||
- **Links**: blue `#1155cc`, underlined.
|
||||
- **Length**: target 2 pages.
|
||||
|
||||
## HTML structure to use
|
||||
|
||||
```html
|
||||
<header class="cv-header">
|
||||
<h1 class="name">OLEG PROSKURIN</h1>
|
||||
<p class="role">Role line</p>
|
||||
<p class="contacts">email | LinkedIn | Telegram | Github | ...</p>
|
||||
</header>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2><!-- exact ## heading from MD, e.g. "Summary" --></h2>
|
||||
<p>...</p>
|
||||
</section>
|
||||
|
||||
<section class="cv-section cv-skills">
|
||||
<h2>Skills</h2>
|
||||
<!-- For each `**Category**` block in MD followed by a `-` list,
|
||||
render the category as <strong> then a <ul> of bullets verbatim. -->
|
||||
<p><strong>Category</strong></p>
|
||||
<ul>
|
||||
<li>Item from MD bullet (text verbatim, including ":" and punctuation)</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2><!-- e.g. "Experience" --></h2>
|
||||
<article class="cv-job">
|
||||
<!-- ### Company — Role → split on first " — " -->
|
||||
<div class="cv-job-header">
|
||||
<span class="company">Company</span>
|
||||
<span class="role">Role</span>
|
||||
</div>
|
||||
<!-- "**Dates** · Location" line → location left, dates right -->
|
||||
<div class="cv-job-meta">
|
||||
<span>Location</span>
|
||||
<span class="dates">Dates verbatim from MD (e.g. "Feb 2025 – Present")</span>
|
||||
</div>
|
||||
<!-- Full description paragraph from MD (do not truncate). Italic. -->
|
||||
<p class="cv-job-desc">Full descriptor paragraph from MD...</p>
|
||||
<ul>
|
||||
<li>Bullet text verbatim from MD (keep <strong>...</strong>: punctuation as-is)</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2><!-- e.g. "Education" --></h2>
|
||||
<p>...</p>
|
||||
</section>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2><!-- e.g. "Languages" --></h2>
|
||||
<p>...</p>
|
||||
</section>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Language**: every artifact (HTML, MD, PDF, tracking rows, card notes) is in English. Always.
|
||||
- **Filenames**: kebab-case. CVs: `cv-YYYY-MM-base.md` for base, `cv.md` inside `tailored/<slug>/` for tailored.
|
||||
- **Dates**: absolute (`2026-05-24`), never "last Thursday".
|
||||
- **Don't edit the reference PDF.** It is the immutable visual anchor.
|
||||
- **Don't auto-regenerate PDFs** unless Oleg asks — show diffs first when content changes.
|
||||
- **Verify before claiming "done"**: after `pnpm pdf`, confirm the PDF exists and looks correct (open it, check page count). If you can't visually verify, say so.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# CV-2026
|
||||
|
||||
Personal CV generation pipeline for Oleg Proskurin.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
base/*.md -> output/html/*.html -> output/pdf/*.pdf
|
||||
(Claude generates) (puppeteer renders)
|
||||
```
|
||||
|
||||
## Folder layout
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `base/` | Master CV in Markdown (source of truth for content) |
|
||||
| `base/reference/` | Reference PDF that defines the visual style/format |
|
||||
| `templates/` | Shared HTML/CSS — used by every generated CV |
|
||||
| `tailored/<company>/` | Company-specific tailored versions (MD + HTML + PDF) |
|
||||
| `tracking/` | Applications, outreaches, communications log |
|
||||
| `scripts/` | Build tooling (PDF generator) |
|
||||
| `output/html/` | Generated HTML (transient, gitignored) |
|
||||
| `output/pdf/` | Generated PDFs (committed) |
|
||||
|
||||
## Usage
|
||||
|
||||
Install once:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Generate PDF from an HTML file:
|
||||
|
||||
```bash
|
||||
pnpm pdf output/html/cv-2026-05-base.html
|
||||
# -> output/pdf/cv-2026-05-base.pdf
|
||||
```
|
||||
|
||||
The HTML file should be generated by Claude from the Markdown source — see `CLAUDE.md` for the full flow and styling rules.
|
||||
|
||||
## Workflows
|
||||
|
||||
1. **New base CV**: drop a new `cv-YYYY-MM-base.md` into `base/`, ask Claude to generate the HTML, then run `pnpm pdf`.
|
||||
2. **Tailor for a company**: ask Claude to create `tailored/<company>/cv.md` from the latest base, generate HTML, then PDF.
|
||||
3. **Track applications**: append to `tracking/applications.md` and `tracking/outreach.md`.
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# OLEG PROSKURIN
|
||||
**AI Engineer & Tech Lead**
|
||||
|
||||
usulpro@gmail.com · Telegram @usulpro · [LinkedIn](https://www.linkedin.com/in/oleg-proskurin-76784453/) · [GitHub](https://github.com/UsulPro) · [Dev.to](https://dev.to/usulpro) · [Blog](https://focusreactive.com/blog/author/usulpro/)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
AI Engineer with 9+ years of production engineering across Frontend, Backend, Serverless, Node.js, and Headless CMS / eCommerce platforms, including 5+ years leading remote projects for US, UK, EU, and AU clients. Currently Tech Lead on PrimeUI ([primeui.com](https://primeui.com/)), an AI-native website generation platform launched Feb 2026: 3 independent AI flows (chat, page generation, content writing) orchestrating Claude / GPT / Gemini via Mastra, MCP server as integration bridge, RAG-based component selection, and a code-export pipeline that ships production-ready Next.js sites.
|
||||
|
||||
---
|
||||
|
||||
## Skills
|
||||
|
||||
**AI Engineering**
|
||||
- LLM orchestration: multi-model routing, context engineering, prompt caching
|
||||
- MCP: server & client development, multi-host integration
|
||||
- RAG & retrieval: embeddings, vector retrieval, hybrid fallback
|
||||
- Eval & observability: guardrails, schema validation, retry recovery
|
||||
- AI-augmented development workflows
|
||||
|
||||
**Data & Infrastructure**
|
||||
- PostgreSQL, Drizzle ORM, Docker
|
||||
- Vercel, Cloudflare, Serverless & Edge functions
|
||||
|
||||
**Headless CMS & eCommerce**
|
||||
- Sanity, Storyblok, Contentful, Payload, Shopify, Stripe
|
||||
- Content modeling, multi-environment publishing, migrations
|
||||
|
||||
**Web & Full-Stack**
|
||||
- TypeScript, Node.js, React, Next.js (App Router, SSR/SSG/ISR)
|
||||
- TanStack Start/Router, Tailwind, shadcn-style design systems
|
||||
|
||||
---
|
||||
|
||||
## Experience
|
||||
|
||||
### PixelPoint Ltd. — Tech Lead & AI Engineer, PrimeUI
|
||||
**Feb 2025 – Present** · Remote
|
||||
AI-native website generation platform ([primeui.com](https://primeui.com/), launched Feb 2026) building professional-grade, production-ready code. Drove ~70% of platform architecture across AI layer, sitemap canvas, server runtime, public API, MCP server, CLI, code-export pipeline, and component registry.
|
||||
|
||||
- **Cut client-wireframe turnaround at PixelPoint** from a week of Figma prototyping to a few hours: deployed prototypes with real desktop/mobile responsive components, production-level web performance, and dynamic interactions instead of static mockups.
|
||||
- **Designed, built, and published MCP server** (`@primeuicom/mcp` on NPM, official TypeScript SDK) as the integration layer letting any external codebase (no PrimeUI export required) connect to PrimeUI from inside AI coding environments. 25 tools exposing project management and AI-driven page creation. Tested and tuned across 6 major AI coding agents, including Claude Code, Cursor, and Codex.
|
||||
- **Architected end-to-end AI generation layer**: multi-model orchestration via Mastra + Vercel AI SDK Gateway (10 agents, 17 workflows), with per-task model routing: Claude Opus 4.6 (Studio chat), GPT-5.2 (outline + section writer), Gemini 2.5 Flash with cached component library (block planning), Claude Haiku 4.5 (short-form tasks).
|
||||
- **Built RAG-based component retrieval**: 1,555 markdown chunks across 204 components, indexed with OpenAI `text-embedding-3-small` (1536d, 256/32 overlap), cosine top-K with three-tier fallback (exact key → family bucket → semantic).
|
||||
- **Built quality guardrails and observability for AI page generation**: deterministic rule-based UX scoring for candidate ranking, JSON + props-schema validation with retry recovery (5 retries; triggers: parse, schema, rate-limit), per-step token / latency / cache-hit logging.
|
||||
- **Optimized cost-quality balance**: 73% Gemini context-cache hit rate via 24-hour TTL on the component library, ~150K input tokens per page variant across ~9 components and ~5 retrievals, 2–3 min end-to-end latency for the default 3-variant page generation.
|
||||
- **Designed code-export pipeline and CLI**: generates a project repo in ~45 sec, ~1.5 min end-to-end from UI Export to a running local project. Output is a production-ready Next.js application with embedded AI tooling, customer-owned and pre-configured for 8 selectable AI coding agents.
|
||||
- **Drove team-wide AI-augmented development**: introduced Claude Code from project start, authored team skills and CLAUDE.md conventions, led the team's transition to Codex-based workflow ahead of public release. Built [codex-bee](https://github.com/usulpro/codex-bee) (CLI wrapper for long-running Codex sessions) and [epic-loop](https://github.com/usulpro/epic-loop) (Codex skill framework with tech-lead and developer agent roles for autonomous large-task-list execution) as a result.
|
||||
|
||||
### FocusReactive — Tech Lead & Senior Software Engineer
|
||||
**May 2019 – Feb 2025** (5 yrs 10 mos) · London · Remote
|
||||
International web consultancy for clients in the US, UK, Europe, and Australia.
|
||||
|
||||
- **Architected Tipico US backend platform on Sanity Headless CMS**: 5 publishing environments, 3 datasets, white-label architecture for core + state teams. Operating scale: 122 CMS users, 75M requests/month, 98 document types, 165K+ stored documents.
|
||||
- **Led migration of 15+ client projects to Headless CMS platforms** (Sanity, Storyblok, Contentful, Payload), restructuring content models and reducing update cycles from weeks to days. Used Claude.ai to analyze inherited codebases, identifying weak points and integration surfaces ahead of migration design.
|
||||
- **Built a Claude-based content automation pipeline**: JSON translation flow for 190 country-specific Storyblok pages, integrated into the publishing process and saving ~1 month of manual content work.
|
||||
- **Designed and built CMS-KIT**, an open-source Headless CMS starter and component library used across 15+ client projects, reducing kickoff from days to hours and accelerating development by ~30%.
|
||||
- **Led technical hiring and team development**: designed an assessment system mirroring real working conditions, conducted 30+ interviews resulting in 8 hires, mentored new engineers; drove team adoption of Claude.ai for research, content, and codebase analysis from 2024 onwards.
|
||||
|
||||
### GitNation — Senior Software Engineer (Part-time, concurrent)
|
||||
**May 2019 – Feb 2025** · Remote
|
||||
Built publishing flow via Slack API and a reusable web framework for conference websites; migrated GitNation conferences to Headless CMS.
|
||||
|
||||
### Osome — Frontend Developer
|
||||
**Jun 2018 – May 2019** · Singapore
|
||||
Built fuzzy-logic search system and advanced UI animations for the corporate-services platform.
|
||||
|
||||
### Skipp — JavaScript Engineer
|
||||
**Nov 2017 – Jun 2018** · Moscow
|
||||
B2B marketplace for Skolkovo Innovation Center (100+ companies integrated); GraphQL-subscription chat for car owners and lessees.
|
||||
|
||||
### BL Group — Head of Engineering Team
|
||||
**2010 – 2017** · Moscow
|
||||
Led an engineering team designing automation and control systems for dynamic architectural lighting (50+ installations).
|
||||
|
||||
---
|
||||
|
||||
## Open Source
|
||||
|
||||
**Storybook** — contributor and steering team member during 2016–2017 community revival; addon maintainer (**storybook-addon-console** — 7M+ NPM downloads; **storybook-addon-material-ui** — 18K weekly).
|
||||
**AI developer tooling** — [codex-bee](https://github.com/usulpro/codex-bee), [epic-loop](https://github.com/usulpro/epic-loop).
|
||||
|
||||
---
|
||||
|
||||
## Education
|
||||
|
||||
**Master in Engineering**, Faculty of Electronic Engineering — Moscow Power Engineering University (MPEI), Russia
|
||||
|
||||
---
|
||||
|
||||
## Languages
|
||||
|
||||
English C1 · Russian native
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "cv-2026",
|
||||
"version": "1.0.0",
|
||||
"description": "CV generation pipeline: Markdown -> HTML -> PDF, with tailoring for specific companies.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"pdf": "node scripts/generate-pdf.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"puppeteer": "^24.37.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,870 @@
|
|||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
puppeteer:
|
||||
specifier: ^24.37.3
|
||||
version: 24.43.1
|
||||
|
||||
packages:
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5':
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@puppeteer/browsers@2.13.2':
|
||||
resolution: {integrity: sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0':
|
||||
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
|
||||
|
||||
'@types/node@25.9.1':
|
||||
resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==}
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
ast-types@0.13.4:
|
||||
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
b4a@1.8.1:
|
||||
resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==}
|
||||
peerDependencies:
|
||||
react-native-b4a: '*'
|
||||
peerDependenciesMeta:
|
||||
react-native-b4a:
|
||||
optional: true
|
||||
|
||||
bare-events@2.8.3:
|
||||
resolution: {integrity: sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==}
|
||||
peerDependencies:
|
||||
bare-abort-controller: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-abort-controller:
|
||||
optional: true
|
||||
|
||||
bare-fs@4.7.1:
|
||||
resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==}
|
||||
engines: {bare: '>=1.16.0'}
|
||||
peerDependencies:
|
||||
bare-buffer: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-buffer:
|
||||
optional: true
|
||||
|
||||
bare-os@3.9.1:
|
||||
resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==}
|
||||
engines: {bare: '>=1.14.0'}
|
||||
|
||||
bare-path@3.0.0:
|
||||
resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==}
|
||||
|
||||
bare-stream@2.13.1:
|
||||
resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==}
|
||||
peerDependencies:
|
||||
bare-abort-controller: '*'
|
||||
bare-buffer: '*'
|
||||
bare-events: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-abort-controller:
|
||||
optional: true
|
||||
bare-buffer:
|
||||
optional: true
|
||||
bare-events:
|
||||
optional: true
|
||||
|
||||
bare-url@2.4.3:
|
||||
resolution: {integrity: sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==}
|
||||
|
||||
basic-ftp@5.3.1:
|
||||
resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
|
||||
callsites@3.1.0:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
chromium-bidi@14.0.0:
|
||||
resolution: {integrity: sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==}
|
||||
peerDependencies:
|
||||
devtools-protocol: '*'
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
cosmiconfig@9.0.1:
|
||||
resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
typescript: '>=4.9.5'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
data-uri-to-buffer@6.0.2:
|
||||
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
degenerator@5.0.1:
|
||||
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
devtools-protocol@0.0.1608973:
|
||||
resolution: {integrity: sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
env-paths@2.2.1:
|
||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
error-ex@1.3.4:
|
||||
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
|
||||
|
||||
escalade@3.2.0:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
escodegen@2.1.0:
|
||||
resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
|
||||
engines: {node: '>=6.0'}
|
||||
hasBin: true
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
estraverse@5.3.0:
|
||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
esutils@2.0.3:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
events-universal@1.0.1:
|
||||
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
||||
|
||||
extract-zip@2.0.1:
|
||||
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
|
||||
engines: {node: '>= 10.17.0'}
|
||||
hasBin: true
|
||||
|
||||
fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
|
||||
fd-slicer@1.1.0:
|
||||
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-stream@5.2.0:
|
||||
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
get-uri@6.0.5:
|
||||
resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
ip-address@10.2.0:
|
||||
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
lru-cache@7.18.3:
|
||||
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
netmask@2.1.1:
|
||||
resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
pac-proxy-agent@7.2.0:
|
||||
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
pac-resolver@7.0.1:
|
||||
resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
parent-module@1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
parse-json@5.2.0:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
progress@2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
proxy-agent@6.5.0:
|
||||
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
pump@3.0.4:
|
||||
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||
|
||||
puppeteer-core@24.43.1:
|
||||
resolution: {integrity: sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
puppeteer@24.43.1:
|
||||
resolution: {integrity: sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
semver@7.8.1:
|
||||
resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
socks-proxy-agent@8.0.5:
|
||||
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
socks@2.8.9:
|
||||
resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
streamx@2.25.0:
|
||||
resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
tar-fs@3.1.2:
|
||||
resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==}
|
||||
|
||||
tar-stream@3.2.0:
|
||||
resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==}
|
||||
|
||||
teex@1.0.1:
|
||||
resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
|
||||
|
||||
text-decoder@1.2.7:
|
||||
resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
typed-query-selector@2.12.2:
|
||||
resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==}
|
||||
|
||||
undici-types@7.24.6:
|
||||
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
||||
|
||||
webdriver-bidi-protocol@0.4.1:
|
||||
resolution: {integrity: sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
ws@8.21.0:
|
||||
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@17.7.2:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yauzl@2.10.0:
|
||||
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@puppeteer/browsers@2.13.2':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
extract-zip: 2.0.1
|
||||
progress: 2.0.3
|
||||
proxy-agent: 6.5.0
|
||||
semver: 7.8.1
|
||||
tar-fs: 3.1.2
|
||||
yargs: 17.7.2
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- bare-buffer
|
||||
- react-native-b4a
|
||||
- supports-color
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
||||
|
||||
'@types/node@25.9.1':
|
||||
dependencies:
|
||||
undici-types: 7.24.6
|
||||
optional: true
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 25.9.1
|
||||
optional: true
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
ast-types@0.13.4:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
b4a@1.8.1: {}
|
||||
|
||||
bare-events@2.8.3: {}
|
||||
|
||||
bare-fs@4.7.1:
|
||||
dependencies:
|
||||
bare-events: 2.8.3
|
||||
bare-path: 3.0.0
|
||||
bare-stream: 2.13.1(bare-events@2.8.3)
|
||||
bare-url: 2.4.3
|
||||
fast-fifo: 1.3.2
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
bare-os@3.9.1: {}
|
||||
|
||||
bare-path@3.0.0:
|
||||
dependencies:
|
||||
bare-os: 3.9.1
|
||||
|
||||
bare-stream@2.13.1(bare-events@2.8.3):
|
||||
dependencies:
|
||||
streamx: 2.25.0
|
||||
teex: 1.0.1
|
||||
optionalDependencies:
|
||||
bare-events: 2.8.3
|
||||
transitivePeerDependencies:
|
||||
- react-native-b4a
|
||||
|
||||
bare-url@2.4.3:
|
||||
dependencies:
|
||||
bare-path: 3.0.0
|
||||
|
||||
basic-ftp@5.3.1: {}
|
||||
|
||||
buffer-crc32@0.2.13: {}
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
chromium-bidi@14.0.0(devtools-protocol@0.0.1608973):
|
||||
dependencies:
|
||||
devtools-protocol: 0.0.1608973
|
||||
mitt: 3.0.1
|
||||
zod: 3.25.76
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
cosmiconfig@9.0.1:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
|
||||
data-uri-to-buffer@6.0.2: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
degenerator@5.0.1:
|
||||
dependencies:
|
||||
ast-types: 0.13.4
|
||||
escodegen: 2.1.0
|
||||
esprima: 4.0.1
|
||||
|
||||
devtools-protocol@0.0.1608973: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
|
||||
error-ex@1.3.4:
|
||||
dependencies:
|
||||
is-arrayish: 0.2.1
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
escodegen@2.1.0:
|
||||
dependencies:
|
||||
esprima: 4.0.1
|
||||
estraverse: 5.3.0
|
||||
esutils: 2.0.3
|
||||
optionalDependencies:
|
||||
source-map: 0.6.1
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
estraverse@5.3.0: {}
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
events-universal@1.0.1:
|
||||
dependencies:
|
||||
bare-events: 2.8.3
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
|
||||
extract-zip@2.0.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
get-stream: 5.2.0
|
||||
yauzl: 2.10.0
|
||||
optionalDependencies:
|
||||
'@types/yauzl': 2.10.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
fast-fifo@1.3.2: {}
|
||||
|
||||
fd-slicer@1.1.0:
|
||||
dependencies:
|
||||
pend: 1.2.0
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-stream@5.2.0:
|
||||
dependencies:
|
||||
pump: 3.0.4
|
||||
|
||||
get-uri@6.0.5:
|
||||
dependencies:
|
||||
basic-ftp: 5.3.1
|
||||
data-uri-to-buffer: 6.0.2
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
import-fresh@3.3.1:
|
||||
dependencies:
|
||||
parent-module: 1.0.1
|
||||
resolve-from: 4.0.0
|
||||
|
||||
ip-address@10.2.0: {}
|
||||
|
||||
is-arrayish@0.2.1: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
lru-cache@7.18.3: {}
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
netmask@2.1.1: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
pac-proxy-agent@7.2.0:
|
||||
dependencies:
|
||||
'@tootallnate/quickjs-emscripten': 0.23.0
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
get-uri: 6.0.5
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
pac-resolver: 7.0.1
|
||||
socks-proxy-agent: 8.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
pac-resolver@7.0.1:
|
||||
dependencies:
|
||||
degenerator: 5.0.1
|
||||
netmask: 2.1.1
|
||||
|
||||
parent-module@1.0.1:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
|
||||
parse-json@5.2.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
error-ex: 1.3.4
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
progress@2.0.3: {}
|
||||
|
||||
proxy-agent@6.5.0:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
lru-cache: 7.18.3
|
||||
pac-proxy-agent: 7.2.0
|
||||
proxy-from-env: 1.1.0
|
||||
socks-proxy-agent: 8.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
pump@3.0.4:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
once: 1.4.0
|
||||
|
||||
puppeteer-core@24.43.1:
|
||||
dependencies:
|
||||
'@puppeteer/browsers': 2.13.2
|
||||
chromium-bidi: 14.0.0(devtools-protocol@0.0.1608973)
|
||||
debug: 4.4.3
|
||||
devtools-protocol: 0.0.1608973
|
||||
typed-query-selector: 2.12.2
|
||||
webdriver-bidi-protocol: 0.4.1
|
||||
ws: 8.21.0
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- bare-buffer
|
||||
- bufferutil
|
||||
- react-native-b4a
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
puppeteer@24.43.1:
|
||||
dependencies:
|
||||
'@puppeteer/browsers': 2.13.2
|
||||
chromium-bidi: 14.0.0(devtools-protocol@0.0.1608973)
|
||||
cosmiconfig: 9.0.1
|
||||
devtools-protocol: 0.0.1608973
|
||||
puppeteer-core: 24.43.1
|
||||
typed-query-selector: 2.12.2
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- bare-buffer
|
||||
- bufferutil
|
||||
- react-native-b4a
|
||||
- supports-color
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
semver@7.8.1: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
socks-proxy-agent@8.0.5:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
socks: 2.8.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
socks@2.8.9:
|
||||
dependencies:
|
||||
ip-address: 10.2.0
|
||||
smart-buffer: 4.2.0
|
||||
|
||||
source-map@0.6.1:
|
||||
optional: true
|
||||
|
||||
streamx@2.25.0:
|
||||
dependencies:
|
||||
events-universal: 1.0.1
|
||||
fast-fifo: 1.3.2
|
||||
text-decoder: 1.2.7
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
tar-fs@3.1.2:
|
||||
dependencies:
|
||||
pump: 3.0.4
|
||||
tar-stream: 3.2.0
|
||||
optionalDependencies:
|
||||
bare-fs: 4.7.1
|
||||
bare-path: 3.0.0
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- bare-buffer
|
||||
- react-native-b4a
|
||||
|
||||
tar-stream@3.2.0:
|
||||
dependencies:
|
||||
b4a: 1.8.1
|
||||
bare-fs: 4.7.1
|
||||
fast-fifo: 1.3.2
|
||||
streamx: 2.25.0
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- bare-buffer
|
||||
- react-native-b4a
|
||||
|
||||
teex@1.0.1:
|
||||
dependencies:
|
||||
streamx: 2.25.0
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
text-decoder@1.2.7:
|
||||
dependencies:
|
||||
b4a: 1.8.1
|
||||
transitivePeerDependencies:
|
||||
- react-native-b4a
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
typed-query-selector@2.12.2: {}
|
||||
|
||||
undici-types@7.24.6:
|
||||
optional: true
|
||||
|
||||
webdriver-bidi-protocol@0.4.1: {}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.21.0: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@17.7.2:
|
||||
dependencies:
|
||||
cliui: 8.0.1
|
||||
escalade: 3.2.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yauzl@2.10.0:
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
fd-slicer: 1.1.0
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import puppeteer from 'puppeteer';
|
||||
import { resolve, basename, dirname, extname } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createServer } from 'http';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PROJECT_ROOT = resolve(__dirname, '..');
|
||||
const OUTPUT_DIR = resolve(PROJECT_ROOT, 'output/pdf');
|
||||
|
||||
const MIME_TYPES = {
|
||||
'.html': 'text/html',
|
||||
'.css': 'text/css',
|
||||
'.js': 'application/javascript',
|
||||
'.mjs': 'application/javascript',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.webp': 'image/webp',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.json': 'application/json',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
'.ttf': 'font/ttf',
|
||||
};
|
||||
|
||||
async function generatePdf(htmlPath, outDirOverride) {
|
||||
const absolutePath = resolve(htmlPath);
|
||||
if (!existsSync(absolutePath)) {
|
||||
console.error(`File not found: ${absolutePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const outDir = outDirOverride ? resolve(outDirOverride) : OUTPUT_DIR;
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
|
||||
const pdfName = basename(absolutePath, '.html') + '.pdf';
|
||||
const pdfPath = resolve(outDir, pdfName);
|
||||
|
||||
// Serve the project root so the HTML can load /templates/cv-style.css and any other assets.
|
||||
const server = createServer(async (req, res) => {
|
||||
const filePath = resolve(
|
||||
PROJECT_ROOT,
|
||||
decodeURIComponent(req.url).replace(/^\//, ''),
|
||||
);
|
||||
try {
|
||||
const data = await readFile(filePath);
|
||||
const ext = extname(filePath);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': MIME_TYPES[ext] || 'application/octet-stream',
|
||||
});
|
||||
res.end(data);
|
||||
} catch {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
||||
const port = server.address().port;
|
||||
const relPath = absolutePath.replace(PROJECT_ROOT + '/', '');
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.goto(`http://127.0.0.1:${port}/${relPath}`, {
|
||||
waitUntil: 'networkidle0',
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Wait for web fonts (if any) to be ready before rasterizing.
|
||||
await page.evaluate(async () => {
|
||||
if (document.fonts && document.fonts.ready) {
|
||||
await document.fonts.ready;
|
||||
}
|
||||
});
|
||||
|
||||
await page.pdf({
|
||||
path: pdfPath,
|
||||
format: 'A4',
|
||||
printBackground: true,
|
||||
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
preferCSSPageSize: true,
|
||||
});
|
||||
|
||||
await browser.close();
|
||||
server.close();
|
||||
console.log(`PDF generated: ${pdfPath}`);
|
||||
return pdfPath;
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error('Usage: node generate-pdf.mjs <html-file> [out-dir]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [htmlFile, outDir] = args;
|
||||
generatePdf(htmlFile, outDir);
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# <Company> — <Role>
|
||||
|
||||
Copy this folder to `tailored/<company-slug>/` and fill it in.
|
||||
|
||||
## Posting
|
||||
|
||||
- URL:
|
||||
- Date posted:
|
||||
- Date applied:
|
||||
- Source (LinkedIn / referral / direct):
|
||||
|
||||
## Contacts
|
||||
|
||||
- Recruiter:
|
||||
- Hiring manager:
|
||||
- Referral:
|
||||
|
||||
## Why this role
|
||||
|
||||
Bullet a few sentences — what makes this a good match, what hooks to lead with.
|
||||
|
||||
## Tailoring decisions
|
||||
|
||||
What was changed vs. base CV and why:
|
||||
|
||||
- Summary: ...
|
||||
- Skills order: ...
|
||||
- Bullets reordered/edited: ...
|
||||
|
||||
## Timeline
|
||||
|
||||
- 2026-MM-DD — applied
|
||||
- 2026-MM-DD — ...
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* CV style — mirrors the visual format of base/reference/Oleg_Proskurin_Techlead_resume.docx.pdf.
|
||||
*
|
||||
* Key conventions:
|
||||
* - Serif (Times-like) body font, ~10.5pt, dense single-line spacing.
|
||||
* - A4 page, ~12mm margins. Single column.
|
||||
* - Centered name + role at the top, single-line contact row.
|
||||
* - Section headings: ALL CAPS bold with bottom underline.
|
||||
* - Job header: company (bold left) | role (bold right), then italic subtitle | dates right.
|
||||
* - Bullets: disc, hanging indent.
|
||||
* - Links: blue, underlined.
|
||||
*/
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 12mm 14mm;
|
||||
}
|
||||
|
||||
:root {
|
||||
--text: #000;
|
||||
--link: #1155cc;
|
||||
--rule: #000;
|
||||
--muted: #333;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Times New Roman', Times, 'Liberation Serif', serif;
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.25;
|
||||
color: var(--text);
|
||||
background: #fff;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ───── Header ───── */
|
||||
|
||||
.cv-header {
|
||||
text-align: center;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
|
||||
.cv-header .name {
|
||||
font-size: 14pt;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5pt;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cv-header .role {
|
||||
font-size: 11pt;
|
||||
font-weight: 700;
|
||||
margin: 2pt 0 4pt 0;
|
||||
}
|
||||
|
||||
.cv-header .contacts {
|
||||
font-size: 10pt;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cv-header .contacts a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ───── Section ───── */
|
||||
|
||||
.cv-section {
|
||||
margin-top: 8pt;
|
||||
}
|
||||
|
||||
.cv-section > h2 {
|
||||
font-size: 10.5pt;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin: 0 0 4pt 0;
|
||||
padding-bottom: 1pt;
|
||||
border-bottom: 0.5pt solid var(--rule);
|
||||
}
|
||||
|
||||
/* ───── Summary / plain paragraph ───── */
|
||||
|
||||
.cv-section p {
|
||||
margin: 0 0 4pt 0;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* ───── Skills block ───── */
|
||||
|
||||
.cv-skills p {
|
||||
margin: 0 0 3pt 0;
|
||||
}
|
||||
|
||||
.cv-skills .skills-category {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ───── Job entries ───── */
|
||||
|
||||
.cv-job {
|
||||
margin-top: 6pt;
|
||||
}
|
||||
|
||||
/* Keep the job header block (company/role, meta row, descriptor) together
|
||||
and glued to the first bullet, so the section title never dangles alone
|
||||
at the bottom of a page. Individual bullets can still flow across pages. */
|
||||
.cv-job-header,
|
||||
.cv-job-meta,
|
||||
.cv-job-desc {
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.cv-job li {
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.cv-job-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 12pt;
|
||||
}
|
||||
|
||||
.cv-job-header .company {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cv-job-header .role {
|
||||
font-weight: 700;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cv-job-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 12pt;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
margin-top: 1pt;
|
||||
}
|
||||
|
||||
.cv-job-meta .dates {
|
||||
font-style: italic;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cv-job-desc {
|
||||
font-style: italic;
|
||||
margin: 2pt 0 0 0;
|
||||
}
|
||||
|
||||
.cv-job ul {
|
||||
margin: 3pt 0 0 0;
|
||||
padding-left: 16pt;
|
||||
}
|
||||
|
||||
.cv-job li {
|
||||
margin: 0 0 2pt 0;
|
||||
padding-left: 2pt;
|
||||
}
|
||||
|
||||
.cv-job li::marker {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* ───── Portfolio / Education / Languages ───── */
|
||||
|
||||
.cv-inline-list {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cv-portfolio a + a::before {
|
||||
content: ', ';
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ───── Misc ───── */
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Oleg Proskurin — CV</title>
|
||||
<link rel="stylesheet" href="/templates/cv-style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="cv-header">
|
||||
<h1 class="name">OLEG PROSKURIN</h1>
|
||||
<p class="role">Senior/Lead Full-stack Software Engineer</p>
|
||||
<p class="contacts">
|
||||
<a href="mailto:usulpro@gmail.com">usulpro@gmail.com</a> |
|
||||
<a href="https://www.linkedin.com/in/oleg-proskurin-76784453/">LinkedIn</a> |
|
||||
Telegram: <a href="https://t.me/usulpro">@usulpro</a> |
|
||||
<a href="https://github.com/UsulPro">Github</a>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2>Professional Summary</h2>
|
||||
<p>One short paragraph. Keep it dense — role, years, focus areas, current scale-of-impact.</p>
|
||||
</section>
|
||||
|
||||
<section class="cv-section cv-skills">
|
||||
<h2>Skills</h2>
|
||||
<p><span class="skills-category">Category A</span>: comma-separated list of tools/platforms.</p>
|
||||
<p><span class="skills-category">Category B</span>: comma-separated list of tools/platforms.</p>
|
||||
<p><span class="skills-category">Languages</span>: English C1, Russian native</p>
|
||||
</section>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2>Work History</h2>
|
||||
|
||||
<article class="cv-job">
|
||||
<div class="cv-job-header">
|
||||
<span class="company"><a href="https://example.com">Company</a> — City/Country</span>
|
||||
<span class="role">Role Title</span>
|
||||
</div>
|
||||
<div class="cv-job-meta">
|
||||
<span>Short company descriptor</span>
|
||||
<span class="dates">MM/YYYY — Current</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Bullet point — start with a verb, quantify impact when possible.</li>
|
||||
<li>Another bullet — concrete result, not responsibility.</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2>Portfolio</h2>
|
||||
<p class="cv-portfolio">
|
||||
<a href="#">Project 1</a>, <a href="#">Project 2</a>, <a href="#">Project 3</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="cv-section">
|
||||
<h2>Education</h2>
|
||||
<p><a href="#">Master in Engineering</a>, Faculty of Electronic Engineering, Moscow Power Engineering University (<a href="#">MPEI</a>) — Moscow, Russia</p>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Applications
|
||||
|
||||
One row per application. Append; never rewrite history.
|
||||
|
||||
Statuses: `applied` → `screen` → `interview` → `offer` / `rejected` / `withdrawn` / `ghosted`
|
||||
|
||||
| Date | Company | Role | Channel | CV used | Status | Last update | Notes |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 2026-MM-DD | Example Co | Senior FE Engineer | LinkedIn / referral / direct | `tailored/example-co/cv.pdf` | applied | 2026-MM-DD | Link to JD, recruiter name |
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Outreach & communications
|
||||
|
||||
Cold outreach, recruiter pings, follow-ups. One row per touchpoint.
|
||||
|
||||
| Date | Company | Person | Channel | Direction | Topic | Next step | Notes |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 2026-MM-DD | Example Co | Jane Doe (recruiter) | LinkedIn | inbound / outbound | initial chat / follow-up / scheduling | reply by YYYY-MM-DD | Short context |
|
||||
Loading…
Reference in New Issue