Skip to content

Commit

Permalink
chore: improve output
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Dec 13, 2024
1 parent ca2b087 commit d295b03
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 125 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"devDependencies": {
"@stacksjs/eslint-config": "^3.8.1-beta.2",
"@stacksjs/rpx": "^0.6.5",
"@stacksjs/rpx": "^0.7.1",
"@types/bun": "^1.1.14",
"bumpp": "^9.9.0",
"bun-plugin-dtsx": "^0.21.9",
Expand Down
284 changes: 160 additions & 124 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,78 @@ import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import process from 'node:process'
import { promisify } from 'node:util'
import { cleanup, startProxies } from '@stacksjs/rpx'
import colors from 'picocolors'
import { checkExistingCertificates, checkHosts, cleanup, startProxies } from '@stacksjs/rpx'
import colors, { dim } from 'picocolors'
import packageJson from '../package.json'
import { buildConfig } from './utils'

function getPackageVersions() {
let viteVersion
const vitePluginLocalVersion = packageJson.version
let vitePressVersion

try {
// Try to get Vite version from node_modules first
// Try to get VitePress version first
const vitePressPath = join(process.cwd(), 'node_modules', 'vitepress', 'package.json')
try {
const vitePressPackage = JSON.parse(readFileSync(vitePressPath, 'utf-8'))
vitePressVersion = vitePressPackage.version
}
catch { }

// Get Vite version
const vitePackageJson = readFileSync(
join(process.cwd(), 'node_modules', 'vite', 'package.json'),
'utf-8',
)
viteVersion = JSON.parse(vitePackageJson).version
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (error) {
// Fallback to package.json dependencies
viteVersion = packageJson.devDependencies?.vite?.replace('^', '')
|| '0.0.0'
viteVersion = packageJson.devDependencies?.vite?.replace('^', '') || '0.0.0'
}

return {
'vitepress': vitePressVersion,
'vite': viteVersion,
'vite-plugin-local': packageJson.version,
'vite-plugin-local': vitePluginLocalVersion,
}
}

const execAsync = promisify(exec)

// Simple sudo validation
async function validateSudo(): Promise<boolean> {
async function needsSudoAccess(options: VitePluginLocalOptions, domain: string): Promise<boolean> {
try {
await execAsync('sudo -n true')
return true
// Check if we need to generate certificates
if (options.https) {
const config = buildConfig(options, 'localhost:5173') // temporary URL for config
const existingCerts = await checkExistingCertificates(config)
if (!existingCerts) {
return true
}
}

// Check if we need to modify hosts file
if (!domain.includes('localhost') && !domain.includes('127.0.0.1')) {
const hostsExist = await checkHosts([domain], options.verbose)
// Only need sudo if hosts don't exist and we don't have write permission
if (!hostsExist[0]) {
try {
// Try to write a test file to check permissions
await execAsync('touch /etc/hosts', { stdio: 'ignore' })
return false
}
catch {
return true
}
}
}

return false
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (error) {
return false
console.error('Error checking sudo requirements:', error)
return false // Changed to false - if we can't check, don't assume we need sudo
}
}

Expand All @@ -54,33 +87,21 @@ export function VitePluginLocal(options: VitePluginLocalOptions): Plugin {
verbose = false,
etcHostsCleanup = true,
} = options

let domains: string[] | undefined
let proxyUrl: string | undefined
let originalConsole: typeof console
let cleanupPromise: Promise<void> | null = null
let isCleaningUp = false

const debug = (...args: any[]) => {
if (verbose)
originalConsole.log('[vite-plugin-local]', ...args)
}

// Override the library's process.exit
const originalExit = process.exit
process.exit = ((code?: number) => {
if (cleanupPromise) {
cleanupPromise.finally(() => {
process.exit = originalExit
process.exit(code)
})
return undefined as never
}
return originalExit(code)
}) as (code?: number) => never

// Add cleanup handler for process exit
const exitHandler = async () => {
if (domains?.length) {
if (domains?.length && !isCleaningUp) {
isCleaningUp = true
debug('Cleaning up...')
cleanupPromise = cleanup({
domains,
Expand All @@ -90,9 +111,23 @@ export function VitePluginLocal(options: VitePluginLocalOptions): Plugin {
await cleanupPromise
domains = undefined
debug('Cleanup complete')
isCleaningUp = false
}
}

// Override the library's process.exit
const originalExit = process.exit
process.exit = ((code?: number) => {
if (cleanupPromise || domains?.length) {
exitHandler().finally(() => {
process.exit = originalExit
process.exit(code)
})
return undefined as never
}
return originalExit(code)
}) as (code?: number) => never

// Handle cleanup for different termination signals
process.on('SIGINT', exitHandler)
process.on('SIGTERM', exitHandler)
Expand All @@ -102,124 +137,125 @@ export function VitePluginLocal(options: VitePluginLocalOptions): Plugin {
name: 'vite-plugin-local',
enforce: 'pre',

config(config) {
if (!enabled)
return config

config.server = config.server || {}
config.server.middlewareMode = false

return config
},

async configureServer(server: ViteDevServer) {
configureServer(server: ViteDevServer) {
if (!enabled)
return

try {
originalConsole = { ...console }

debug('Checking sudo access...')
const hasSudo = await validateSudo()

if (!hasSudo) {
console.log('\nSudo access required for proxy setup.')
console.log('Please enter your password when prompted.\n')

try {
await execAsync('sudo true')
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (error) {
console.error('Failed to get sudo access. Please try again.')
process.exit(1)
}
// Override console.log immediately to prevent VitePress initial messages
const originalLog = console.log
console.log = (...args) => {
if (typeof args[0] === 'string' && (
args[0].includes('vitepress v')
|| args[0].includes('press h to show help')
)) {
return
}
originalLog.apply(console, args)
}

debug('Sudo access validated')
// Store original console for debug
originalConsole = { ...console }

const setupProxy = async () => {
try {
const host = typeof server.config.server.host === 'boolean'
? 'localhost'
: server.config.server.host || 'localhost'
server.printUrls = () => { }

const port = server.config.server.port || 5173
const serverUrl = `${host}:${port}`
const setupPlugin = async () => {
try {
const config = buildConfig(options, 'localhost:5173')
const domain = config.to

const config = buildConfig(options, serverUrl)
domains = [config.to]
proxyUrl = config.to
// Check if we need sudo
const needsSudo = await needsSudoAccess(options, domain)

// Suppress all console output during proxy setup
const noOp = () => { }
console.log = noOp
console.info = noOp
console.warn = noOp
if (needsSudo) {
debug('Sudo access required')
// Only show sudo message if we actually need it
console.log('\nSudo access required for proxy setup.')
console.log('Please enter your password when prompted.\n')

debug('Starting proxies...')
await startProxies(config)
try {
await execAsync('sudo true')
}
catch (error) {
console.error('Failed to get sudo access. Please try again.')
process.exit(1)
}
}

// Restore console
console.log = originalConsole.log
console.info = originalConsole.info
console.warn = originalConsole.warn
const setupProxy = async () => {
try {
const host = typeof server.config.server.host === 'boolean'
? 'localhost'
: server.config.server.host || 'localhost'

// Custom print URLs function
server.printUrls = function () {
const protocol = options.https ? 'https' : 'http'
const port = server.config.server.port || 5173
const localUrl = `http://localhost:${port}/`
const proxiedUrl = `${protocol}://${proxyUrl}/`

const colorUrl = (url: string) =>
colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`))

const versions = getPackageVersions()

console.log(
`\n${colors.bold(colors.green('vite'))} ${colors.green(`v${versions.vite}`)} ${colors.italic(
colors.green('&'),
)} ${colors.bold(colors.green('vite-plugin-local'))} ${colors.green(`v${versions['vite-plugin-local']}`)}\n`,
)

console.log(` ${colors.green('➜')} ${colors.bold('Local')}: ${colorUrl(localUrl)}`)
console.log(` ${colors.green('➜')} ${colors.bold('Proxied')}: ${colorUrl(proxiedUrl)}`)

if (options.https) {
console.log(` ${colors.green('➜')} ${colors.bold('SSL')}: ${colors.dim('TLS 1.2/1.3, HTTP/2')}`)
const serverUrl = `${host}:${port}`

const config = buildConfig(options, serverUrl)
domains = [config.to]
proxyUrl = config.to

debug('Starting proxies...')

await startProxies(config)

server.printUrls = function () {
const protocol = options.https ? 'https' : 'http'
const port = server.config.server.port || 5173
const localUrl = `http://localhost:${port}/`
const proxiedUrl = `${protocol}://${proxyUrl}/`
const colorUrl = (url: string) =>
colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`))

const versions = getPackageVersions()
if (versions.vitepress) {
console.log(
`\n ${colors.bold(colors.green('vitepress'))} ${colors.green(`v${versions.vitepress}`)} via ${colors.bold(colors.green('vite-plugin-local'))} ${colors.green(`v${versions['vite-plugin-local']}`)}\n`,
)
}
else {
console.log(
`\n ${colors.bold(colors.green('vite'))} ${colors.green(`v${versions.vite}`)} via ${colors.bold(colors.green('vite-plugin-local'))} ${colors.green(`v${versions['vite-plugin-local']}`)}\n`,
)
}

console.log(` ${colors.green('➜')} ${colors.bold('Local')}: ${colorUrl(localUrl)}`)
console.log(` ${colors.green('➜')} ${colors.bold('Proxied')}: ${colorUrl(proxiedUrl)}`)

if (options.https) {
console.log(` ${colors.green('➜')} ${colors.bold('SSL')}: ${colors.dim('TLS 1.2/1.3, HTTP/2')}`)
}

console.log(
colors.dim(` ${colors.green('➜')} ${colors.bold('Network')}: use `)
+ colors.bold('--host')
+ colors.dim(' to expose'),
)

console.log(`\n ${colors.green('➜')} ${dim('press')} ${colors.bold('h')} ${dim('to show help')}\n`)
}

console.log(
colors.dim(` ${colors.green('➜')} ${colors.bold('Network')}: use `)
+ colors.bold('--host')
+ colors.dim(' to expose'),
)

console.log(`\n ${colors.green('➜')} press ${colors.bold('h')} to show help\n`)
server.printUrls()
debug('Proxy setup complete')
}
catch (error) {
console.error('Failed to start reverse proxy:', error)
process.exit(1)
}

server.printUrls()
debug('Proxy setup complete')
}
catch (error) {
console.error('Failed to start reverse proxy:', error)

server.httpServer?.once('listening', setupProxy)
if (server.httpServer?.listening) {
debug('Server already listening, setting up proxy immediately')
await setupProxy()
}
}

server.httpServer?.once('listening', setupProxy)
if (server.httpServer?.listening) {
debug('Server already listening, setting up proxy immediately')
await setupProxy()
catch (error) {
console.error('Failed to initialize plugin:', error)
process.exit(1)
}
}
catch (error) {
console.error('Failed to initialize plugin:', error)
}

server.httpServer?.once('close', async () => {
await exitHandler()
})
return setupPlugin()
},
}
}
Expand Down

0 comments on commit d295b03

Please sign in to comment.