Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Mar 8, 2024
1 parent 712a5bf commit 2786e8d
Show file tree
Hide file tree
Showing 12 changed files with 80 additions and 66 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ node_modules
temp
.vitepress/dist
.vitepress/cache
storage
rp
1 change: 1 addition & 0 deletions .vscode/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ outdir
pausable
pkgx
Postcardware
postcompile
prefetch
preinstall
socio
Expand Down
19 changes: 13 additions & 6 deletions bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface Options {
to?: string
keyPath?: string
certPath?: string
verbose?: boolean
}

cli
Expand All @@ -21,6 +22,7 @@ cli
.option('--to <to>', 'The URL to proxy to')
.option('--keyPath <path>', 'Absolute path to the SSL key')
.option('--certPath <path>', 'Absolute path to the SSL certificate')
.option('--verbose', 'Enable verbose logging', { default: false })
.example('reverse-proxy start --from localhost:3000 --to my-project.localhost')
.example('reverse-proxy start --from localhost:3000 --to localhost:3001')
.example('reverse-proxy start --from localhost:3000 --to my-project.test --keyPath /absolute/path/to/key --certPath /absolute/path/to/cert')
Expand Down Expand Up @@ -59,33 +61,38 @@ cli
.example('sudo reverse-proxy update:etc-hosts')
.example('sudo reverse-proxy update-etc-hosts')
.action(async () => {
log.info('Ensuring /etc/hosts file covers the proxy domains...')
log.info('Ensuring /etc/hosts file covers the proxy domain/s...')

const hostsFilePath = os.platform() === 'win32'
? 'C:\\Windows\\System32\\drivers\\etc\\hosts'
: '/etc/hosts'

if (config && typeof config === 'object') {
const entriesToAdd = Object.entries(config).map(([from, to]) => `127.0.0.1 ${to} # reverse-proxy mapping for ${from}`)
// Ensure "127.0.0.1 localhost" is in the array
entriesToAdd.push('127.0.0.1 localhost # essential localhost mapping')

try {
let currentHostsContent = readFileSync(hostsFilePath, 'utf8')
let updated = false

for (const entry of entriesToAdd) {
const to = entry.split(' ')[1]
const [ip, host] = entry.split(' ', 2)
// Use a regex to match the line with any amount of whitespace between IP and host
const regex = new RegExp(`^${ip}\\s+${host.split(' ')[0]}(\\s|$)`, 'm')
// Check if the entry (domain) is already in the file
if (!currentHostsContent.includes(to)) {
// If not, append it
if (!regex.test(currentHostsContent)) {
// If not, append it
currentHostsContent += `\n${entry}`
updated = true
}
else {
log.info(`Entry for ${to} already exists in the hosts file.`)
log.info(`Entry for ${host} already exists in the hosts file.`)
}
}

if (updated) {
writeFileSync(hostsFilePath, currentHostsContent, 'utf8')

log.success('Hosts file updated with latest proxy domains.')
}
else {
Expand Down
Binary file modified bun.lockb
Binary file not shown.
18 changes: 9 additions & 9 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ import { defineConfig } from 'vitepress'

// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Reverse Proxy",
description: "A better developer environment.",
title: 'Reverse Proxy',
description: 'A better developer environment.',
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/markdown-examples' }
{ text: 'Examples', link: '/markdown-examples' },
],

sidebar: [
{
text: 'Examples',
items: [
{ text: 'Markdown Examples', link: '/markdown-examples' },
{ text: 'Runtime API Examples', link: '/api-examples' }
]
}
{ text: 'Runtime API Examples', link: '/api-examples' },
],
},
],

socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' },
],
},
})
1 change: 0 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ features:
- title: Feature C
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
---

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@
"src"
],
"scripts": {
"build": "bun build.ts && bun run compile",
"compile": "bun build ./bin/cli.ts --compile --minify --sourcemap --outfile dist/reverse-proxy && mv ./bin/reverse-proxy ./dist/reverse-proxy",
"build": "bun scripts/build.ts && bun run compile",
"compile": "bun build ./bin/cli.ts --compile --external rollup --minify --sourcemap --outfile dist/reverse-proxy",
"postcompile": "bun ./scripts/post-compile.ts",
"lint": "eslint .",
"lint:fix": "bunx eslint . --fix",
"fresh": "bunx rimraf node_modules/ bun.lock && bun i",
Expand All @@ -61,12 +62,12 @@
"docs:preview": "vitepress preview docs"
},
"dependencies": {
"@stacksjs/cli": "^0.59.9",
"@stacksjs/storage": "^0.59.9",
"@stacksjs/cli": "^0.59.11",
"@stacksjs/storage": "^0.59.11",
"c12": "^1.9.0"
},
"devDependencies": {
"@stacksjs/development": "^0.59.9",
"@stacksjs/development": "^0.59.11",
"@types/bun": "^1.0.8",
"@types/node": "^20.11.24",
"bun-plugin-dts-auto": "^0.10.0",
Expand Down
14 changes: 9 additions & 5 deletions build.ts → scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import path from 'node:path'
import { $ } from 'bun'
import dts from 'bun-plugin-dts-auto'
import { log } from '@stacksjs/logging'

console.log('Building...')
log.info('Building...')

$.cwd(path.resolve(import.meta.dir, '..'))
await $`rm -rf ./dist`

await Bun.build({
entrypoints: ['./src/index.ts', './bin/cli.ts'],
outdir: './dist',
format: 'esm',
target: 'bun',
external: ['rollup', 'fsevents'],

plugins: [
dts({
cwd: import.meta.dir,
cwd: path.resolve(import.meta.dir, '..'),
}),
],
})
Expand All @@ -22,7 +28,5 @@ await $`cp ./dist/bin/cli.js ./dist/cli.js`
await $`rm -rf ./dist/bin`
await $`cp ./bin/cli.d.ts ./dist/cli.d.ts` // while bun-plugin-dts-auto doesn't support bin files well
await $`rm ./bin/cli.d.ts`
await $`cp ./bin/reverse-proxy ./dist/reverse-proxy`
await $`rm ./bin/reverse-proxy`

console.log('Build done!')
log.success('Built')
4 changes: 4 additions & 0 deletions scripts/post-compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { $ } from 'bun'

await $`mv ./bin/reverse-proxy ./dist/reverse-proxy`
await $`cp ./dist/reverse-proxy ./rp`
73 changes: 34 additions & 39 deletions src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import * as net from 'node:net'
import * as http from 'node:http'
import * as https from 'node:https'
import * as fs from 'node:fs'
import process from 'node:process'
import type { Buffer } from 'node:buffer'
import path from 'node:path'
import { bold, green, log, runCommand } from '@stacksjs/cli'
import { path } from '@stacksjs/path'
import { bold, dim, green, log, runCommand } from '@stacksjs/cli'
import { version } from '../package.json'

interface Option {
export interface Option {
from?: string // domain to proxy from, defaults to localhost:3000
to?: string // domain to proxy to, defaults to stacks.localhost
keyPath?: string // absolute path to the key
Expand All @@ -21,38 +20,8 @@ type Options = Option | Option[]
export async function startServer(option: Option = { from: 'localhost:3000', to: 'stacks.localhost' }): Promise<void> {
log.debug('Starting Reverse Proxy Server')

let key: Buffer | undefined
let cert: Buffer | undefined

const keyPath = option.keyPath ?? path.resolve(import.meta.dir, `keys/localhost-key.pem`)
if (fs.existsSync(keyPath))
key = fs.readFileSync(keyPath)
else
log.debug('No SSL key found')

const certPath = option.certPath ?? path.resolve(import.meta.dir, `keys/localhost.pem`)
if (fs.existsSync(certPath))
cert = fs.readFileSync(certPath)
else
log.debug('No SSL certificate found')

if (!fs.existsSync(keyPath) || fs.existsSync(certPath)) {
log.info('A valid SSL key & certificate was not found')
log.info('Creating a self-signed certificate...')

// self-sign a certificate using mkcert
const keysPath = path.resolve(import.meta.dir, 'keys')
if (!fs.existsSync(keysPath))
fs.mkdirSync(keysPath)

await runCommand('mkcert -install', {
cwd: keysPath,
})

await runCommand(`mkcert *.${option.to}`, {
cwd: keysPath,
})
}
// Ensure the SSL key and certificate exist
const { key, cert } = await ensureCertificates(option)

// Parse the option.from URL to dynamically set hostname and port
const fromUrl = new URL(option.from ? (option.from.startsWith('http') ? option.from : `http://${option.from}`) : 'http://localhost:3000')
Expand All @@ -74,7 +43,7 @@ export async function startServer(option: Option = { from: 'localhost:3000', to:
})
}

function setupReverseProxy({ key, cert, hostname, port, option }: { key?: Buffer, cert?: Buffer, hostname: string, port: number, option: Option }): void {
export function setupReverseProxy({ key, cert, hostname, port, option }: { key?: Buffer, cert?: Buffer, hostname: string, port: number, option: Option }): void {
// This server will act as a reverse proxy
const httpsServer = https.createServer({ key, cert }, (req, res) => {
// Define the target server's options
Expand Down Expand Up @@ -113,15 +82,15 @@ function setupReverseProxy({ key, cert, hostname, port, option }: { key?: Buffer
// eslint-disable-next-line no-console
console.log('')
// eslint-disable-next-line no-console
console.log(` ${green('➜')} ${option.from}${option.to}`)
console.log(` ${green('➜')} ${dim(option.from!)} ${dim('➜')} https://${option.to}`)
})

// http to https redirect
if (option.httpsRedirect ?? true)
startHttpRedirectServer()
}

function startHttpRedirectServer(): void {
export function startHttpRedirectServer(): void {
http.createServer((req, res) => {
res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` })
res.end()
Expand All @@ -142,3 +111,29 @@ export function startProxies(options?: Options): void {
startServer(options)
}
}

export async function ensureCertificates(option: Option): Promise<{ key: Buffer, cert: Buffer }> {
const sslBasePath = path.homeDir('.stacks/ssl')
const keysPath = path.resolve(sslBasePath, 'keys')
await fs.promises.mkdir(keysPath, { recursive: true })

const keyPath = option.keyPath ?? path.resolve(keysPath, `${option.to}-key.pem`)
const certPath = option.certPath ?? path.resolve(keysPath, `${option.to}.pem`)

let key: Buffer | undefined
let cert: Buffer | undefined

try {
key = await fs.promises.readFile(keyPath)
cert = await fs.promises.readFile(certPath)
}
catch (error) {
log.info('A valid SSL key & certificate was not found, creating a self-signed certificate...')
await runCommand('mkcert -install', { cwd: keysPath })
await runCommand(`mkcert ${option.to}`, { cwd: keysPath })
key = await fs.promises.readFile(keyPath)
cert = await fs.promises.readFile(certPath)
}

return { key, cert }
}
1 change: 0 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-expect-error types are somehow missing
import { describe, expect, it } from 'bun:test'

describe('should', () => {
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"strict": true,
"strictNullChecks": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"noEmit": true,
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
Expand Down

0 comments on commit 2786e8d

Please sign in to comment.