Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rich-chefs-wear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Added `.compose` support to HTML payment links.
84 changes: 65 additions & 19 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ packages:
overrides:
mppx: 'workspace:*'
vitest: 'npm:@voidzero-dev/vite-plus-test@~0.1.14'
typescript: $typescript
ox: '0.14.7'
viem: '^2.47.5'
path-to-regexp@<8.4.0: '8.4.0'
Expand Down
45 changes: 45 additions & 0 deletions scripts/build:html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@ const stripeMode = process.env.STRIPE_HTML_MODE ?? defaultMode
const formatBundleSize = (bytes: number) =>
bytes >= 1_000 ? `${(bytes / 1_000).toFixed(1)} kB` : `${bytes} B`

// Tab script (bundled as raw JS string for compose HTML)
// Must be built before HTML entries since they import config.ts which re-exports tabScript
{
const entry = 'src/server/internal/html/compose.main.ts'
const outFile = path.resolve(root, 'src/server/internal/html/compose.main.gen.ts')

await build({
input: path.resolve(root, entry),
output: {
dir: outDir,
format: 'iife',
minify: true,
},
})

const jsFile = fs.readdirSync(outDir).find((f) => f.endsWith('.js'))
if (!jsFile) throw new Error(`No .js output found for ${entry}`)

const code = fs.readFileSync(path.join(outDir, jsFile), 'utf8').trim()
const bundleBytes = Buffer.byteLength(code)
const content = `// Generated — do not edit.\nexport const tabScript = ${JSON.stringify(`<script>${code}</script>`)}\n`

fs.writeFileSync(outFile, content)
fs.rmSync(outDir, { recursive: true })
console.log(`wrote ${path.relative(root, outFile)} (${formatBundleSize(bundleBytes)})`)
}

// HTML entries — bundled into <script> tags
const htmlEntries = [
{
Expand All @@ -24,6 +51,13 @@ const htmlEntries = [
},
]

// Markers that only exist inside `import.meta.env.MODE === 'test'` branches.
// If any survive bundling in non-test mode, dead code elimination failed.
const testOnlyMarkers: Record<string, string[]> = {
'src/stripe/server/internal/html/main.ts': ['pm_card_visa'],
'src/tempo/server/internal/html/main.ts': ['generatePrivateKey'],
}

for (const { entry, mode, outFile } of htmlEntries) {
await build({
input: path.resolve(root, entry),
Expand Down Expand Up @@ -51,6 +85,17 @@ for (const { entry, mode, outFile } of htmlEntries) {
const bundleBytes = Buffer.byteLength(code)
const content = `// Generated — do not edit.\nexport const html = ${JSON.stringify(`<script>${code}</script>`)}\n`

// Confirm test-only dead code was eliminated for non-test builds
if (mode !== 'test') {
const markers = testOnlyMarkers[entry] ?? []
const leaked = markers.filter((m) => code.includes(m))
if (leaked.length > 0)
throw new Error(
`Dead code elimination failed for ${entry} (mode=${mode}). ` +
`Test-only markers found in bundle: ${leaked.join(', ')}`,
)
}

fs.writeFileSync(outFile, content)
fs.rmSync(outDir, { recursive: true })
console.log(`wrote ${path.relative(root, outFile)} (${formatBundleSize(bundleBytes)})`)
Expand Down
Loading
Loading