pdfx-cli 0.4.0 → 0.4.2
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/README.md +73 -0
- package/dist/index.js +94 -58
- package/dist/mcp/index.js +34 -15
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# pdfx-cli
|
|
2
|
+
|
|
3
|
+
> The official CLI tool for [PDFx](https://pdfx.akashpise.dev), professional pre-built PDF React components.
|
|
4
|
+
|
|
5
|
+
Create beautiful, dynamic, and perfectly typed PDFs in React using standard Tailwind-like utility classes and strict property interfaces. PDFx provides a library of copy-pasteable blocks (Invoices, Reports, Receipts) that you fully own and customize inside your project.
|
|
6
|
+
|
|
7
|
+
Built on top of [@react-pdf/renderer](https://react-pdf.org/).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Initialize the PDFx configuration and setup in your project:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx pdfx-cli@latest init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Adding Components
|
|
18
|
+
|
|
19
|
+
Add specific PDFx components directly into your local codebase. You own the code!
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx pdfx-cli@latest add badge
|
|
23
|
+
npx pdfx-cli@latest add table form qrcode
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Available Components
|
|
27
|
+
- `alert`
|
|
28
|
+
- `badge`
|
|
29
|
+
- `card`
|
|
30
|
+
- `data-table`
|
|
31
|
+
- `divider`
|
|
32
|
+
- `form`
|
|
33
|
+
- `heading`
|
|
34
|
+
- `keep-together`
|
|
35
|
+
- `key-value`
|
|
36
|
+
- `link`
|
|
37
|
+
- `list`
|
|
38
|
+
- `page-break`
|
|
39
|
+
- `page-footer`
|
|
40
|
+
- `page-header`
|
|
41
|
+
- `page-number`
|
|
42
|
+
- `pdf-image`
|
|
43
|
+
- `qrcode`
|
|
44
|
+
- `section`
|
|
45
|
+
- `signature`
|
|
46
|
+
- `stack`
|
|
47
|
+
- `table`
|
|
48
|
+
- `text`
|
|
49
|
+
- `watermark`
|
|
50
|
+
|
|
51
|
+
## Pre-Composed Blocks
|
|
52
|
+
Start with full, gorgeous templates.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx pdfx-cli@latest block add invoice-modern
|
|
56
|
+
npx pdfx-cli@latest block add report-financial
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## MCP & AI Agents
|
|
60
|
+
|
|
61
|
+
PDFx comes with first-class AI Agent integration via MCP (Model Context Protocol). Connect your AI IDE or Agent directly to the PDFx registry to instantly gain fluent context about PDFx component structures.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx pdfx-cli@latest mcp init --client cursor
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
Full documentation, real-time PDF previews, and block gallery available at [pdfx.akashpise.dev](https://pdfx.akashpise.dev).
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5
5
|
import { dirname, join } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import
|
|
7
|
+
import chalk11 from "chalk";
|
|
8
8
|
import { Command as Command3 } from "commander";
|
|
9
9
|
|
|
10
10
|
// src/commands/add.ts
|
|
@@ -2218,6 +2218,7 @@ import { existsSync } from "fs";
|
|
|
2218
2218
|
import { mkdir, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
2219
2219
|
import path11 from "path";
|
|
2220
2220
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2221
|
+
import chalk8 from "chalk";
|
|
2221
2222
|
import { Command } from "commander";
|
|
2222
2223
|
import prompts5 from "prompts";
|
|
2223
2224
|
|
|
@@ -2385,8 +2386,21 @@ async function getAuditChecklist() {
|
|
|
2385
2386
|
\`\`\`
|
|
2386
2387
|
|
|
2387
2388
|
### "Invalid hook call"
|
|
2388
|
-
|
|
2389
|
-
|
|
2389
|
+
\`@react-pdf/renderer\` renders synchronously. Hooks that depend on browser APIs or
|
|
2390
|
+
async side effects do not work in the PDF render path.
|
|
2391
|
+
|
|
2392
|
+
**Do NOT use inside a PDFx component:**
|
|
2393
|
+
- \`useEffect\` / \`useLayoutEffect\` — no browser lifecycle in PDF rendering
|
|
2394
|
+
- \`useRef\` for DOM nodes — no DOM exists in the PDF render tree
|
|
2395
|
+
- Any hook that calls browser globals (\`window\`, \`document\`, \`navigator\`)
|
|
2396
|
+
|
|
2397
|
+
**These ARE valid inside PDFx components:**
|
|
2398
|
+
- PDFx framework hooks: \`usePdfxTheme()\`, \`useSafeMemo()\`
|
|
2399
|
+
- Custom hooks that are pure functions with no browser-API dependencies
|
|
2400
|
+
- Data passed down as props from a parent client component
|
|
2401
|
+
|
|
2402
|
+
If you see this error in a wrapper component you wrote, move the browser hook
|
|
2403
|
+
to the nearest client component and pass the result down as a prop.
|
|
2390
2404
|
|
|
2391
2405
|
### "Text strings must be rendered inside \`<Text>\` component"
|
|
2392
2406
|
Wrap all string literals in \`<Text>\` from \`@react-pdf/renderer\`:
|
|
@@ -2586,7 +2600,7 @@ async function getComponent(args) {
|
|
|
2586
2600
|
const primaryContent = item.files[0]?.content ?? "";
|
|
2587
2601
|
const primaryPath = item.files[0]?.path ?? "";
|
|
2588
2602
|
const exportNames = extractAllExportNames(primaryContent);
|
|
2589
|
-
const mainExport = extractExportName(primaryContent);
|
|
2603
|
+
const mainExport = extractExportName(primaryContent, args.component);
|
|
2590
2604
|
const exportSection = exportNames.length > 0 ? dedent4`
|
|
2591
2605
|
## Exports
|
|
2592
2606
|
**Main component export:** \`${mainExport ?? exportNames[0]}\`
|
|
@@ -2631,11 +2645,18 @@ async function getComponent(args) {
|
|
|
2631
2645
|
${fileSources}
|
|
2632
2646
|
`);
|
|
2633
2647
|
}
|
|
2634
|
-
function extractExportName(source) {
|
|
2648
|
+
function extractExportName(source, componentName) {
|
|
2635
2649
|
if (!source) return null;
|
|
2636
|
-
const matches = [...source.matchAll(/export\s+
|
|
2637
|
-
|
|
2638
|
-
|
|
2650
|
+
const matches = [...source.matchAll(/export\s+function\s+([A-Z][A-Za-z0-9]*)/g)];
|
|
2651
|
+
const names = matches.map((m) => m[1]).filter(Boolean);
|
|
2652
|
+
if (names.length === 0) return null;
|
|
2653
|
+
if (!componentName) return names[0];
|
|
2654
|
+
const norm = componentName.replace(/-/g, "").toLowerCase();
|
|
2655
|
+
const exact = names.find((n) => n.toLowerCase() === norm);
|
|
2656
|
+
if (exact) return exact;
|
|
2657
|
+
const suffix = names.find((n) => n.toLowerCase().endsWith(norm));
|
|
2658
|
+
if (suffix) return suffix;
|
|
2659
|
+
return names[0];
|
|
2639
2660
|
}
|
|
2640
2661
|
function extractAllExportNames(source) {
|
|
2641
2662
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2844,7 +2865,7 @@ async function getInstallation(args) {
|
|
|
2844
2865
|
| TypeScript errors on \`@react-pdf/renderer\` | \`${installCmd(pm, "@react-pdf/types", true)}\` |
|
|
2845
2866
|
| "Cannot find module @/components/pdfx/..." | Run \`npx pdfx-cli@latest add <component>\` to install it |
|
|
2846
2867
|
| PDF renders blank | Ensure root returns \`<Document><Page>...</Page></Document>\` |
|
|
2847
|
-
| "Invalid hook call" |
|
|
2868
|
+
| "Invalid hook call" | Browser-only hooks (\`useEffect\`, \`useRef\` for DOM) don't work in the PDF render path — move them to a parent client component and pass the result as a prop. PDFx framework hooks (\`usePdfxTheme\`, \`useSafeMemo\`) are valid inside PDF components. |
|
|
2848
2869
|
|
|
2849
2870
|
---
|
|
2850
2871
|
Next: call \`get_audit_checklist\` to verify your setup is correct.
|
|
@@ -2968,14 +2989,13 @@ async function getTheme(args) {
|
|
|
2968
2989
|
|
|
2969
2990
|
## Usage in Components
|
|
2970
2991
|
\`\`\`tsx
|
|
2971
|
-
// Access theme values in a PDFx component
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
theme: PdfxTheme;
|
|
2976
|
-
}
|
|
2992
|
+
// Access theme values in a PDFx component via the usePdfxTheme hook.
|
|
2993
|
+
// @pdfx/shared is workspace-only — do NOT import from it in your components.
|
|
2994
|
+
import { View, Text } from '@react-pdf/renderer';
|
|
2995
|
+
import { usePdfxTheme } from '../lib/pdfx-theme-context';
|
|
2977
2996
|
|
|
2978
|
-
export function MyComponent(
|
|
2997
|
+
export function MyComponent() {
|
|
2998
|
+
const theme = usePdfxTheme();
|
|
2979
2999
|
return (
|
|
2980
3000
|
<View style={{ backgroundColor: theme.colors.background }}>
|
|
2981
3001
|
<Text style={{ color: theme.colors.foreground, fontSize: theme.typography.body.fontSize }}>
|
|
@@ -3213,6 +3233,14 @@ function isPdfxAlreadyConfigured(config) {
|
|
|
3213
3233
|
return withMcpServers.mcpServers?.pdfx !== void 0 || withServers.servers?.pdfx !== void 0 || withMcp.mcp?.pdfx !== void 0;
|
|
3214
3234
|
}
|
|
3215
3235
|
async function initMcpConfig(opts) {
|
|
3236
|
+
const preFlightResult = runPreFlightChecks();
|
|
3237
|
+
displayPreFlightResults(preFlightResult);
|
|
3238
|
+
if (!preFlightResult.canProceed) {
|
|
3239
|
+
console.error(
|
|
3240
|
+
chalk8.red("\n Cannot proceed due to blocking issues. Please fix them and try again.\n")
|
|
3241
|
+
);
|
|
3242
|
+
process.exit(1);
|
|
3243
|
+
}
|
|
3216
3244
|
let clientName = opts.client;
|
|
3217
3245
|
if (!clientName) {
|
|
3218
3246
|
const response = await prompts5({
|
|
@@ -3294,7 +3322,7 @@ mcpCommand.command("init").description("Add PDFx MCP server config to your AI ed
|
|
|
3294
3322
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
3295
3323
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
3296
3324
|
import path12 from "path";
|
|
3297
|
-
import
|
|
3325
|
+
import chalk9 from "chalk";
|
|
3298
3326
|
import { Command as Command2 } from "commander";
|
|
3299
3327
|
import prompts6 from "prompts";
|
|
3300
3328
|
|
|
@@ -3997,6 +4025,14 @@ function fileHasPdfxContent(filePath) {
|
|
|
3997
4025
|
}
|
|
3998
4026
|
}
|
|
3999
4027
|
async function skillsInit(opts) {
|
|
4028
|
+
const preFlightResult = runPreFlightChecks();
|
|
4029
|
+
displayPreFlightResults(preFlightResult);
|
|
4030
|
+
if (!preFlightResult.canProceed) {
|
|
4031
|
+
console.error(
|
|
4032
|
+
chalk9.red("\n Cannot proceed due to blocking issues. Please fix them and try again.\n")
|
|
4033
|
+
);
|
|
4034
|
+
process.exit(1);
|
|
4035
|
+
}
|
|
4000
4036
|
let platformName = opts.platform;
|
|
4001
4037
|
if (!platformName) {
|
|
4002
4038
|
const response = await prompts6({
|
|
@@ -4016,10 +4052,10 @@ async function skillsInit(opts) {
|
|
|
4016
4052
|
}
|
|
4017
4053
|
const platform = PLATFORMS.find((p) => p.name === platformName);
|
|
4018
4054
|
if (!platform) {
|
|
4019
|
-
process.stderr.write(
|
|
4055
|
+
process.stderr.write(chalk9.red(`\u2716 Unknown platform: "${platformName}"
|
|
4020
4056
|
`));
|
|
4021
4057
|
process.stderr.write(
|
|
4022
|
-
|
|
4058
|
+
chalk9.dim(` Valid options: ${PLATFORMS.map((p) => p.name).join(", ")}
|
|
4023
4059
|
`)
|
|
4024
4060
|
);
|
|
4025
4061
|
process.exit(1);
|
|
@@ -4043,7 +4079,7 @@ async function skillsInit(opts) {
|
|
|
4043
4079
|
]
|
|
4044
4080
|
});
|
|
4045
4081
|
if (!action2 || action2 === "skip") {
|
|
4046
|
-
process.stdout.write(
|
|
4082
|
+
process.stdout.write(chalk9.dim("\nSkipped \u2014 existing file kept.\n\n"));
|
|
4047
4083
|
return;
|
|
4048
4084
|
}
|
|
4049
4085
|
} else {
|
|
@@ -4061,7 +4097,7 @@ async function skillsInit(opts) {
|
|
|
4061
4097
|
]
|
|
4062
4098
|
});
|
|
4063
4099
|
if (!action2 || action2 === "skip") {
|
|
4064
|
-
process.stdout.write(
|
|
4100
|
+
process.stdout.write(chalk9.dim("\nSkipped \u2014 existing file kept.\n\n"));
|
|
4065
4101
|
return;
|
|
4066
4102
|
}
|
|
4067
4103
|
if (action2 === "append") {
|
|
@@ -4088,15 +4124,15 @@ ${PDFX_SKILLS_CONTENT}`;
|
|
|
4088
4124
|
}
|
|
4089
4125
|
const action = shouldAppend && alreadyExists ? "Appended PDFx context to" : "Wrote";
|
|
4090
4126
|
process.stdout.write(`
|
|
4091
|
-
${
|
|
4127
|
+
${chalk9.green("\u2713")} ${action} ${chalk9.cyan(relativeFile)}
|
|
4092
4128
|
`);
|
|
4093
4129
|
process.stdout.write(`
|
|
4094
|
-
${
|
|
4130
|
+
${chalk9.dim(platform.verifyStep)}
|
|
4095
4131
|
|
|
4096
4132
|
`);
|
|
4097
4133
|
if (platform.name === "claude") {
|
|
4098
4134
|
process.stdout.write(
|
|
4099
|
-
|
|
4135
|
+
chalk9.dim(
|
|
4100
4136
|
"Tip: if you also use the MCP server, CLAUDE.md + MCP gives the AI\nthe best possible PDFx knowledge \u2014 static props reference + live registry.\n\n"
|
|
4101
4137
|
)
|
|
4102
4138
|
);
|
|
@@ -4109,34 +4145,34 @@ skillsCommand.command("init").description("Write the PDFx skills file to your AI
|
|
|
4109
4145
|
).option("-y, --yes", "Overwrite existing file without prompting").option("-a, --append", "Append PDFx context to an existing file instead of overwriting").action(skillsInit);
|
|
4110
4146
|
skillsCommand.command("list").description("List all supported AI editor platforms").action(() => {
|
|
4111
4147
|
process.stdout.write("\n");
|
|
4112
|
-
process.stdout.write(
|
|
4148
|
+
process.stdout.write(chalk9.bold(" Supported platforms\n\n"));
|
|
4113
4149
|
for (const p of PLATFORMS) {
|
|
4114
|
-
process.stdout.write(` ${
|
|
4150
|
+
process.stdout.write(` ${chalk9.cyan(p.name.padEnd(12))} ${chalk9.dim("\u2192")} ${p.file}
|
|
4115
4151
|
`);
|
|
4116
4152
|
}
|
|
4117
4153
|
process.stdout.write("\n");
|
|
4118
4154
|
process.stdout.write(
|
|
4119
|
-
|
|
4155
|
+
chalk9.dim(" Usage: npx pdfx-cli@latest skills init --platform <name>\n\n")
|
|
4120
4156
|
);
|
|
4121
4157
|
});
|
|
4122
4158
|
|
|
4123
4159
|
// src/commands/theme.ts
|
|
4124
4160
|
import fs8 from "fs";
|
|
4125
4161
|
import path13 from "path";
|
|
4126
|
-
import
|
|
4162
|
+
import chalk10 from "chalk";
|
|
4127
4163
|
import ora7 from "ora";
|
|
4128
4164
|
import prompts7 from "prompts";
|
|
4129
4165
|
import ts from "typescript";
|
|
4130
4166
|
async function themeInit() {
|
|
4131
4167
|
const configPath = path13.join(process.cwd(), "pdfx.json");
|
|
4132
4168
|
if (!checkFileExists(configPath)) {
|
|
4133
|
-
console.error(
|
|
4134
|
-
console.log(
|
|
4135
|
-
console.log(
|
|
4136
|
-
console.log(
|
|
4169
|
+
console.error(chalk10.red("\nError: pdfx.json not found"));
|
|
4170
|
+
console.log(chalk10.yellow("\n PDFx is not initialized in this project.\n"));
|
|
4171
|
+
console.log(chalk10.cyan(" Run: pdfx init"));
|
|
4172
|
+
console.log(chalk10.dim(" This will set up your project configuration and theme.\n"));
|
|
4137
4173
|
process.exit(1);
|
|
4138
4174
|
}
|
|
4139
|
-
console.log(
|
|
4175
|
+
console.log(chalk10.bold.cyan("\n PDFx Theme Setup\n"));
|
|
4140
4176
|
const answers = await prompts7(
|
|
4141
4177
|
[
|
|
4142
4178
|
{
|
|
@@ -4173,13 +4209,13 @@ async function themeInit() {
|
|
|
4173
4209
|
],
|
|
4174
4210
|
{
|
|
4175
4211
|
onCancel: () => {
|
|
4176
|
-
console.log(
|
|
4212
|
+
console.log(chalk10.yellow("\nTheme setup cancelled."));
|
|
4177
4213
|
process.exit(0);
|
|
4178
4214
|
}
|
|
4179
4215
|
}
|
|
4180
4216
|
);
|
|
4181
4217
|
if (!answers.preset || !answers.themePath) {
|
|
4182
|
-
console.error(
|
|
4218
|
+
console.error(chalk10.red("Missing required fields."));
|
|
4183
4219
|
process.exit(1);
|
|
4184
4220
|
}
|
|
4185
4221
|
const presetName = answers.preset;
|
|
@@ -4199,19 +4235,19 @@ async function themeInit() {
|
|
|
4199
4235
|
if (result.success) {
|
|
4200
4236
|
const updatedConfig = { ...result.data, theme: themePath };
|
|
4201
4237
|
writeFile(configPath, JSON.stringify(updatedConfig, null, 2));
|
|
4202
|
-
console.log(
|
|
4238
|
+
console.log(chalk10.green(" Updated pdfx.json with theme path"));
|
|
4203
4239
|
}
|
|
4204
4240
|
} catch {
|
|
4205
|
-
console.log(
|
|
4241
|
+
console.log(chalk10.yellow(' Could not update pdfx.json \u2014 add "theme" field manually'));
|
|
4206
4242
|
}
|
|
4207
4243
|
}
|
|
4208
|
-
console.log(
|
|
4244
|
+
console.log(chalk10.dim(`
|
|
4209
4245
|
Edit ${themePath} to customize your theme.
|
|
4210
4246
|
`));
|
|
4211
4247
|
} catch (error) {
|
|
4212
4248
|
spinner.fail("Failed to create theme file");
|
|
4213
4249
|
const message = error instanceof Error ? error.message : String(error);
|
|
4214
|
-
console.error(
|
|
4250
|
+
console.error(chalk10.dim(` ${message}`));
|
|
4215
4251
|
process.exit(1);
|
|
4216
4252
|
}
|
|
4217
4253
|
}
|
|
@@ -4219,28 +4255,28 @@ async function themeSwitch(presetName) {
|
|
|
4219
4255
|
const resolvedPreset = presetName === "default" ? "professional" : presetName;
|
|
4220
4256
|
const validPresets = Object.keys(themePresets);
|
|
4221
4257
|
if (!validPresets.includes(resolvedPreset)) {
|
|
4222
|
-
console.error(
|
|
4223
|
-
console.log(
|
|
4258
|
+
console.error(chalk10.red(`\u2716 Invalid theme preset: "${presetName}"`));
|
|
4259
|
+
console.log(chalk10.dim(` Available presets: ${validPresets.join(", ")}, default
|
|
4224
4260
|
`));
|
|
4225
|
-
console.log(
|
|
4261
|
+
console.log(chalk10.dim(" Usage: pdfx theme switch <preset>"));
|
|
4226
4262
|
process.exit(1);
|
|
4227
4263
|
}
|
|
4228
4264
|
const validatedPreset = resolvedPreset;
|
|
4229
4265
|
const configPath = path13.join(process.cwd(), "pdfx.json");
|
|
4230
4266
|
if (!checkFileExists(configPath)) {
|
|
4231
|
-
console.error(
|
|
4267
|
+
console.error(chalk10.red('No pdfx.json found. Run "npx pdfx-cli@latest init" first.'));
|
|
4232
4268
|
process.exit(1);
|
|
4233
4269
|
}
|
|
4234
4270
|
const rawConfig = readJsonFile(configPath);
|
|
4235
4271
|
const result = configSchema.safeParse(rawConfig);
|
|
4236
4272
|
if (!result.success) {
|
|
4237
|
-
console.error(
|
|
4273
|
+
console.error(chalk10.red("Invalid pdfx.json configuration."));
|
|
4238
4274
|
process.exit(1);
|
|
4239
4275
|
}
|
|
4240
4276
|
const config = result.data;
|
|
4241
4277
|
if (!config.theme) {
|
|
4242
4278
|
console.error(
|
|
4243
|
-
|
|
4279
|
+
chalk10.red(
|
|
4244
4280
|
'No theme path in pdfx.json. Run "npx pdfx-cli@latest theme init" to set up theming.'
|
|
4245
4281
|
)
|
|
4246
4282
|
);
|
|
@@ -4253,7 +4289,7 @@ async function themeSwitch(presetName) {
|
|
|
4253
4289
|
initial: false
|
|
4254
4290
|
});
|
|
4255
4291
|
if (!answer.confirm) {
|
|
4256
|
-
console.log(
|
|
4292
|
+
console.log(chalk10.yellow("Cancelled."));
|
|
4257
4293
|
return;
|
|
4258
4294
|
}
|
|
4259
4295
|
const spinner = ora7(`Switching to ${validatedPreset} theme...`).start();
|
|
@@ -4269,7 +4305,7 @@ async function themeSwitch(presetName) {
|
|
|
4269
4305
|
} catch (error) {
|
|
4270
4306
|
spinner.fail("Failed to switch theme");
|
|
4271
4307
|
const message = error instanceof Error ? error.message : String(error);
|
|
4272
|
-
console.error(
|
|
4308
|
+
console.error(chalk10.dim(` ${message}`));
|
|
4273
4309
|
process.exit(1);
|
|
4274
4310
|
}
|
|
4275
4311
|
}
|
|
@@ -4339,18 +4375,18 @@ function parseThemeObject(themePath) {
|
|
|
4339
4375
|
async function themeValidate() {
|
|
4340
4376
|
const configPath = path13.join(process.cwd(), "pdfx.json");
|
|
4341
4377
|
if (!checkFileExists(configPath)) {
|
|
4342
|
-
console.error(
|
|
4378
|
+
console.error(chalk10.red('No pdfx.json found. Run "npx pdfx-cli@latest init" first.'));
|
|
4343
4379
|
process.exit(1);
|
|
4344
4380
|
}
|
|
4345
4381
|
const rawConfig = readJsonFile(configPath);
|
|
4346
4382
|
const configResult = configSchema.safeParse(rawConfig);
|
|
4347
4383
|
if (!configResult.success) {
|
|
4348
|
-
console.error(
|
|
4384
|
+
console.error(chalk10.red("Invalid pdfx.json configuration."));
|
|
4349
4385
|
process.exit(1);
|
|
4350
4386
|
}
|
|
4351
4387
|
if (!configResult.data.theme) {
|
|
4352
4388
|
console.error(
|
|
4353
|
-
|
|
4389
|
+
chalk10.red(
|
|
4354
4390
|
'No theme path in pdfx.json. Run "npx pdfx-cli@latest theme init" to set up theming.'
|
|
4355
4391
|
)
|
|
4356
4392
|
);
|
|
@@ -4358,7 +4394,7 @@ async function themeValidate() {
|
|
|
4358
4394
|
}
|
|
4359
4395
|
const absThemePath = path13.resolve(process.cwd(), configResult.data.theme);
|
|
4360
4396
|
if (!checkFileExists(absThemePath)) {
|
|
4361
|
-
console.error(
|
|
4397
|
+
console.error(chalk10.red(`Theme file not found: ${configResult.data.theme}`));
|
|
4362
4398
|
process.exit(1);
|
|
4363
4399
|
}
|
|
4364
4400
|
const spinner = ora7("Validating theme file...").start();
|
|
@@ -4366,21 +4402,21 @@ async function themeValidate() {
|
|
|
4366
4402
|
const parsedTheme = parseThemeObject(absThemePath);
|
|
4367
4403
|
const result = themeSchema.safeParse(parsedTheme);
|
|
4368
4404
|
if (!result.success) {
|
|
4369
|
-
const issues = result.error.issues.map((issue) => ` \u2192 ${
|
|
4405
|
+
const issues = result.error.issues.map((issue) => ` \u2192 ${chalk10.yellow(issue.path.join("."))}: ${issue.message}`).join("\n");
|
|
4370
4406
|
spinner.fail("Theme validation failed");
|
|
4371
|
-
console.log(
|
|
4407
|
+
console.log(chalk10.red("\n Missing or invalid fields:\n"));
|
|
4372
4408
|
console.log(issues);
|
|
4373
|
-
console.log(
|
|
4409
|
+
console.log(chalk10.dim("\n Fix these fields in your theme file and run validate again.\n"));
|
|
4374
4410
|
process.exit(1);
|
|
4375
4411
|
}
|
|
4376
4412
|
spinner.succeed("Theme file is valid");
|
|
4377
|
-
console.log(
|
|
4413
|
+
console.log(chalk10.dim(`
|
|
4378
4414
|
Validated: ${configResult.data.theme}
|
|
4379
4415
|
`));
|
|
4380
4416
|
} catch (error) {
|
|
4381
4417
|
spinner.fail("Failed to validate theme");
|
|
4382
4418
|
const message = error instanceof Error ? error.message : String(error);
|
|
4383
|
-
console.error(
|
|
4419
|
+
console.error(chalk10.dim(` ${message}`));
|
|
4384
4420
|
process.exit(1);
|
|
4385
4421
|
}
|
|
4386
4422
|
}
|
|
@@ -4400,7 +4436,7 @@ program.name("pdfx").description("CLI for PDFx components").version(getVersion()
|
|
|
4400
4436
|
program.configureOutput({
|
|
4401
4437
|
writeErr: (str) => {
|
|
4402
4438
|
const message = str.replace(/^error:\s*/i, "").trimEnd();
|
|
4403
|
-
process.stderr.write(
|
|
4439
|
+
process.stderr.write(chalk11.red(`\u2716 ${message}
|
|
4404
4440
|
`));
|
|
4405
4441
|
}
|
|
4406
4442
|
});
|
|
@@ -4423,7 +4459,7 @@ try {
|
|
|
4423
4459
|
await program.parseAsync();
|
|
4424
4460
|
} catch (err) {
|
|
4425
4461
|
const message = err instanceof Error ? err.message : String(err);
|
|
4426
|
-
process.stderr.write(
|
|
4462
|
+
process.stderr.write(chalk11.red(`\u2716 ${message}
|
|
4427
4463
|
`));
|
|
4428
4464
|
process.exitCode = 1;
|
|
4429
4465
|
}
|
package/dist/mcp/index.js
CHANGED
|
@@ -570,8 +570,21 @@ async function getAuditChecklist() {
|
|
|
570
570
|
\`\`\`
|
|
571
571
|
|
|
572
572
|
### "Invalid hook call"
|
|
573
|
-
|
|
574
|
-
|
|
573
|
+
\`@react-pdf/renderer\` renders synchronously. Hooks that depend on browser APIs or
|
|
574
|
+
async side effects do not work in the PDF render path.
|
|
575
|
+
|
|
576
|
+
**Do NOT use inside a PDFx component:**
|
|
577
|
+
- \`useEffect\` / \`useLayoutEffect\` — no browser lifecycle in PDF rendering
|
|
578
|
+
- \`useRef\` for DOM nodes — no DOM exists in the PDF render tree
|
|
579
|
+
- Any hook that calls browser globals (\`window\`, \`document\`, \`navigator\`)
|
|
580
|
+
|
|
581
|
+
**These ARE valid inside PDFx components:**
|
|
582
|
+
- PDFx framework hooks: \`usePdfxTheme()\`, \`useSafeMemo()\`
|
|
583
|
+
- Custom hooks that are pure functions with no browser-API dependencies
|
|
584
|
+
- Data passed down as props from a parent client component
|
|
585
|
+
|
|
586
|
+
If you see this error in a wrapper component you wrote, move the browser hook
|
|
587
|
+
to the nearest client component and pass the result down as a prop.
|
|
575
588
|
|
|
576
589
|
### "Text strings must be rendered inside \`<Text>\` component"
|
|
577
590
|
Wrap all string literals in \`<Text>\` from \`@react-pdf/renderer\`:
|
|
@@ -771,7 +784,7 @@ async function getComponent(args) {
|
|
|
771
784
|
const primaryContent = item.files[0]?.content ?? "";
|
|
772
785
|
const primaryPath = item.files[0]?.path ?? "";
|
|
773
786
|
const exportNames = extractAllExportNames(primaryContent);
|
|
774
|
-
const mainExport = extractExportName(primaryContent);
|
|
787
|
+
const mainExport = extractExportName(primaryContent, args.component);
|
|
775
788
|
const exportSection = exportNames.length > 0 ? dedent4`
|
|
776
789
|
## Exports
|
|
777
790
|
**Main component export:** \`${mainExport ?? exportNames[0]}\`
|
|
@@ -816,11 +829,18 @@ async function getComponent(args) {
|
|
|
816
829
|
${fileSources}
|
|
817
830
|
`);
|
|
818
831
|
}
|
|
819
|
-
function extractExportName(source) {
|
|
832
|
+
function extractExportName(source, componentName) {
|
|
820
833
|
if (!source) return null;
|
|
821
|
-
const matches = [...source.matchAll(/export\s+
|
|
822
|
-
|
|
823
|
-
|
|
834
|
+
const matches = [...source.matchAll(/export\s+function\s+([A-Z][A-Za-z0-9]*)/g)];
|
|
835
|
+
const names = matches.map((m) => m[1]).filter(Boolean);
|
|
836
|
+
if (names.length === 0) return null;
|
|
837
|
+
if (!componentName) return names[0];
|
|
838
|
+
const norm = componentName.replace(/-/g, "").toLowerCase();
|
|
839
|
+
const exact = names.find((n) => n.toLowerCase() === norm);
|
|
840
|
+
if (exact) return exact;
|
|
841
|
+
const suffix = names.find((n) => n.toLowerCase().endsWith(norm));
|
|
842
|
+
if (suffix) return suffix;
|
|
843
|
+
return names[0];
|
|
824
844
|
}
|
|
825
845
|
function extractAllExportNames(source) {
|
|
826
846
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1029,7 +1049,7 @@ async function getInstallation(args) {
|
|
|
1029
1049
|
| TypeScript errors on \`@react-pdf/renderer\` | \`${installCmd(pm, "@react-pdf/types", true)}\` |
|
|
1030
1050
|
| "Cannot find module @/components/pdfx/..." | Run \`npx pdfx-cli@latest add <component>\` to install it |
|
|
1031
1051
|
| PDF renders blank | Ensure root returns \`<Document><Page>...</Page></Document>\` |
|
|
1032
|
-
| "Invalid hook call" |
|
|
1052
|
+
| "Invalid hook call" | Browser-only hooks (\`useEffect\`, \`useRef\` for DOM) don't work in the PDF render path — move them to a parent client component and pass the result as a prop. PDFx framework hooks (\`usePdfxTheme\`, \`useSafeMemo\`) are valid inside PDF components. |
|
|
1033
1053
|
|
|
1034
1054
|
---
|
|
1035
1055
|
Next: call \`get_audit_checklist\` to verify your setup is correct.
|
|
@@ -1153,14 +1173,13 @@ async function getTheme(args) {
|
|
|
1153
1173
|
|
|
1154
1174
|
## Usage in Components
|
|
1155
1175
|
\`\`\`tsx
|
|
1156
|
-
// Access theme values in a PDFx component
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
theme: PdfxTheme;
|
|
1161
|
-
}
|
|
1176
|
+
// Access theme values in a PDFx component via the usePdfxTheme hook.
|
|
1177
|
+
// @pdfx/shared is workspace-only — do NOT import from it in your components.
|
|
1178
|
+
import { View, Text } from '@react-pdf/renderer';
|
|
1179
|
+
import { usePdfxTheme } from '../lib/pdfx-theme-context';
|
|
1162
1180
|
|
|
1163
|
-
export function MyComponent(
|
|
1181
|
+
export function MyComponent() {
|
|
1182
|
+
const theme = usePdfxTheme();
|
|
1164
1183
|
return (
|
|
1165
1184
|
<View style={{ backgroundColor: theme.colors.background }}>
|
|
1166
1185
|
<Text style={{ color: theme.colors.foreground, fontSize: theme.typography.body.fontSize }}>
|