Skip to content

Commit dd4cbdd

Browse files
committed
feat: add bun plugin
1 parent db0d5be commit dd4cbdd

File tree

2 files changed

+182
-6
lines changed

2 files changed

+182
-6
lines changed

docs/.vitepress/config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { HeadConfig } from 'vitepress'
22
import { transformerTwoslash } from '@shikijs/vitepress-twoslash'
33
import { withPwa } from '@vite-pwa/vitepress'
44
import { defineConfig } from 'vitepress'
5-
65
import vite from './vite.config'
76

87
// https://vitepress.dev/reference/site-config

packages/bun-plugin/src/index.ts

Lines changed: 182 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,186 @@
1-
import type { BunPlugin } from 'bun'
1+
import type { BunPlugin, ServeOptions } from 'bun'
2+
import { spawn } from 'node:child_process'
3+
import path from 'node:path'
4+
import process from 'node:process'
25

3-
export const plugin: BunPlugin = {
4-
name: 'bun-plugin-rpx',
5-
async setup(_build) {
6-
},
6+
interface RpxPluginOptions {
7+
/**
8+
* The domain to use instead of localhost:port
9+
* @example 'my-app.test', 'awesome.localhost'
10+
* @default '$projectName.localhost'
11+
*/
12+
domain?: string
13+
14+
/**
15+
* Allow HTTPS
16+
* @default true
17+
*/
18+
https?: boolean
19+
20+
/**
21+
* Enable debug logging
22+
* @default false
23+
*/
24+
verbose?: boolean
25+
}
26+
27+
interface ServeFunction {
28+
(options?: ServeOptions): {
29+
start: (...args: unknown[]) => Promise<{ port: number }>
30+
stop: () => Promise<void>
31+
}
32+
}
33+
34+
interface PluginBuilder {
35+
serve: ServeFunction
36+
}
37+
38+
const defaultOptions: Required<RpxPluginOptions> = {
39+
domain: '',
40+
https: true,
41+
verbose: false,
42+
}
43+
44+
/**
45+
* A Bun plugin to provide custom domain names for local development
46+
* instead of using localhost:port
47+
*/
48+
export function plugin(options: RpxPluginOptions = {}): BunPlugin {
49+
const pluginOpts: Required<RpxPluginOptions> = { ...defaultOptions, ...options }
50+
51+
// Store server instance and port to clean up later
52+
let serverPort: number | null = null
53+
let rpxProcess: ReturnType<typeof spawn> | null = null
54+
55+
return {
56+
name: 'bun-plugin-rpx',
57+
async setup(build) {
58+
// Get the project name from package.json as a fallback domain
59+
let projectName = ''
60+
try {
61+
const pkgPath = path.join(process.cwd(), 'package.json')
62+
const pkg = await import(pkgPath, {
63+
with: { type: 'json' },
64+
})
65+
projectName = pkg.default.name || 'app'
66+
}
67+
catch {
68+
projectName = 'app'
69+
}
70+
71+
// Use provided domain or fallback to projectName.localhost
72+
const domain = pluginOpts.domain || `${projectName}.localhost`
73+
74+
// Hook into serve to intercept port and start rpx
75+
const buildWithServe = build as unknown as PluginBuilder
76+
const originalServe = buildWithServe.serve
77+
78+
buildWithServe.serve = (options?: ServeOptions) => {
79+
// Store the original serve function result
80+
const server = originalServe(options)
81+
const originalStart = server.start
82+
83+
server.start = async (...args: unknown[]) => {
84+
// Start the original server
85+
const result = await originalStart.apply(server, args)
86+
87+
// Get the port from the server
88+
serverPort = result.port
89+
90+
if (serverPort) {
91+
await startRpx(domain, serverPort, pluginOpts.https, pluginOpts.verbose)
92+
}
93+
94+
return result
95+
}
96+
97+
// Handle server stop to clean up rpx
98+
const originalStop = server.stop
99+
server.stop = async () => {
100+
if (rpxProcess) {
101+
rpxProcess.kill()
102+
rpxProcess = null
103+
}
104+
105+
return originalStop.apply(server)
106+
}
107+
108+
return server
109+
}
110+
111+
// Handle build process exit
112+
process.on('exit', cleanup)
113+
process.on('SIGINT', () => {
114+
cleanup()
115+
process.exit(0)
116+
})
117+
process.on('SIGTERM', () => {
118+
cleanup()
119+
process.exit(0)
120+
})
121+
},
122+
}
123+
124+
/**
125+
* Start rpx process
126+
*/
127+
async function startRpx(domain: string, port: number, https: boolean, verbose: boolean) {
128+
// Find rpx binary - it should be installed as a dependency
129+
try {
130+
const rpxBinary = 'rpx'
131+
132+
// Build the command to run rpx
133+
const args = [
134+
`--from=localhost:${port}`,
135+
`--to=${domain}`,
136+
]
137+
138+
// Add HTTPS flag if needed
139+
if (https) {
140+
args.push('--https')
141+
}
142+
143+
// Add verbose flag if needed
144+
if (verbose) {
145+
args.push('--verbose')
146+
}
147+
148+
// Start rpx process
149+
rpxProcess = spawn(rpxBinary, args, {
150+
stdio: verbose ? 'inherit' : 'ignore',
151+
detached: false,
152+
})
153+
154+
// Log startup information
155+
console.error(`\n🌐 rpx: ${https ? 'https' : 'http'}://${domain} -> localhost:${port}\n`)
156+
157+
// Handle process events
158+
rpxProcess.on('error', (err) => {
159+
console.error(`rpx error: ${err.message}`)
160+
rpxProcess = null
161+
})
162+
163+
rpxProcess.on('exit', (code) => {
164+
if (code !== 0 && code !== null) {
165+
console.error(`rpx exited with code ${code}`)
166+
}
167+
rpxProcess = null
168+
})
169+
}
170+
catch (error) {
171+
console.error('Failed to start rpx:', error)
172+
}
173+
}
174+
175+
/**
176+
* Clean up rpx process
177+
*/
178+
function cleanup() {
179+
if (rpxProcess) {
180+
rpxProcess.kill()
181+
rpxProcess = null
182+
}
183+
}
7184
}
8185

9186
export default plugin

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy