don't hardcode localhost
This commit is contained in:
parent
d99f80cd0e
commit
6f03954850
|
|
@ -22,10 +22,9 @@ Plug it in, turn it on, and forget about the cloud.
|
||||||
by default, the CLI connects to `localhost:3000` in dev and `toes.local:80` in production.
|
by default, the CLI connects to `localhost:3000` in dev and `toes.local:80` in production.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
toes config # show current host
|
toes config # show current host
|
||||||
TOES_URL=http://192.168.1.50:3000 toes list # full URL
|
TOES_URL=http://192.168.1.50:3000 toes list # connect to IP
|
||||||
TOES_HOST=mypi.local toes list # hostname (port 80)
|
TOES_URL=http://mypi.local toes list # connect to hostname
|
||||||
TOES_HOST=mypi.local PORT=3000 toes list # hostname + port
|
|
||||||
```
|
```
|
||||||
|
|
||||||
set `NODE_ENV=production` to default to `toes.local:80`.
|
set `NODE_ENV=production` to default to `toes.local:80`.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { generateTemplates, type TemplateType } from '%templates'
|
||||||
import color from 'kleur'
|
import color from 'kleur'
|
||||||
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
||||||
import { basename, join } from 'path'
|
import { basename, join } from 'path'
|
||||||
import { del, get, getManifest, HOST, post } from '../http'
|
import { del, get, getManifest, HOST, makeAppUrl, post } from '../http'
|
||||||
import { confirm, prompt } from '../prompts'
|
import { confirm, prompt } from '../prompts'
|
||||||
import { resolveAppName } from '../name'
|
import { resolveAppName } from '../name'
|
||||||
import { pushApp } from './sync'
|
import { pushApp } from './sync'
|
||||||
|
|
@ -20,23 +20,15 @@ export async function configShow() {
|
||||||
|
|
||||||
const source = process.env.TOES_URL
|
const source = process.env.TOES_URL
|
||||||
? 'TOES_URL'
|
? 'TOES_URL'
|
||||||
: process.env.TOES_HOST
|
: process.env.NODE_ENV === 'production'
|
||||||
? 'TOES_HOST' + (process.env.PORT ? ' + PORT' : '')
|
? '(production)'
|
||||||
: process.env.NODE_ENV === 'production'
|
: '(development)'
|
||||||
? 'default (production)'
|
|
||||||
: 'default (development)'
|
|
||||||
|
|
||||||
console.log(`Source: ${color.gray(source)}`)
|
console.log(`Source: ${color.gray(source)}`)
|
||||||
|
|
||||||
if (process.env.TOES_URL) {
|
if (process.env.TOES_URL) {
|
||||||
console.log(` TOES_URL=${process.env.TOES_URL}`)
|
console.log(` TOES_URL=${process.env.TOES_URL}`)
|
||||||
}
|
}
|
||||||
if (process.env.TOES_HOST) {
|
|
||||||
console.log(` TOES_HOST=${process.env.TOES_HOST}`)
|
|
||||||
}
|
|
||||||
if (process.env.PORT) {
|
|
||||||
console.log(` PORT=${process.env.PORT}`)
|
|
||||||
}
|
|
||||||
if (process.env.NODE_ENV) {
|
if (process.env.NODE_ENV) {
|
||||||
console.log(` NODE_ENV=${process.env.NODE_ENV}`)
|
console.log(` NODE_ENV=${process.env.NODE_ENV}`)
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +49,7 @@ export async function infoApp(arg?: string) {
|
||||||
console.log(` State: ${app.state}`)
|
console.log(` State: ${app.state}`)
|
||||||
if (app.port) {
|
if (app.port) {
|
||||||
console.log(` Port: ${app.port}`)
|
console.log(` Port: ${app.port}`)
|
||||||
console.log(` URL: http://localhost:${app.port}`)
|
console.log(` URL: ${makeAppUrl(app.port)}`)
|
||||||
}
|
}
|
||||||
if (app.started) {
|
if (app.started) {
|
||||||
const uptime = Date.now() - app.started
|
const uptime = Date.now() - app.started
|
||||||
|
|
@ -185,7 +177,7 @@ export async function openApp(arg?: string) {
|
||||||
console.error(`App is not running: ${name}`)
|
console.error(`App is not running: ${name}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const url = `http://localhost:${app.port}`
|
const url = makeAppUrl(app.port!)
|
||||||
console.log(`Opening ${url}`)
|
console.log(`Opening ${url}`)
|
||||||
Bun.spawn(['open', url])
|
Bun.spawn(['open', url])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,40 @@ function getDefaultHost(): string {
|
||||||
return `http://localhost:${process.env.PORT ?? 3000}`
|
return `http://localhost:${process.env.PORT ?? 3000}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultPort = process.env.NODE_ENV === 'production' ? 80 : 3000
|
const normalizeUrl = (url: string) =>
|
||||||
|
url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`
|
||||||
|
|
||||||
|
function tryParseError(text: string): string | undefined {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(text)
|
||||||
|
return json.error
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const HOST = process.env.TOES_URL
|
export const HOST = process.env.TOES_URL
|
||||||
?? (process.env.TOES_HOST ? `http://${process.env.TOES_HOST}:${process.env.PORT ?? defaultPort}` : undefined)
|
? normalizeUrl(process.env.TOES_URL)
|
||||||
?? getDefaultHost()
|
: getDefaultHost()
|
||||||
|
|
||||||
export function makeUrl(path: string): string {
|
export function makeUrl(path: string): string {
|
||||||
return `${HOST}${path}`
|
return `${HOST}${path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function makeAppUrl(port: number): string {
|
||||||
|
const url = new URL(HOST)
|
||||||
|
url.port = String(port)
|
||||||
|
return url.toString().replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
export function handleError(error: unknown): void {
|
export function handleError(error: unknown): void {
|
||||||
if (error instanceof Error && 'code' in error && error.code === 'ConnectionRefused') {
|
if (error instanceof Error && 'code' in error && error.code === 'ConnectionRefused') {
|
||||||
console.error(`🐾 Can't connect to toes server at ${HOST}`)
|
console.error(`🐾 Can't connect to toes server at ${HOST}`)
|
||||||
console.error(` Set TOES_URL or TOES_HOST to connect to a different host`)
|
console.error(` Set TOES_URL to connect to a different host`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`Error: ${error.message}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|
@ -29,7 +49,11 @@ export function handleError(error: unknown): void {
|
||||||
export async function get<T>(url: string): Promise<T | undefined> {
|
export async function get<T>(url: string): Promise<T | undefined> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(makeUrl(url))
|
const res = await fetch(makeUrl(url))
|
||||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
if (!res.ok) {
|
||||||
|
const text = await res.text()
|
||||||
|
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
return await res.json()
|
return await res.json()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
|
|
@ -55,7 +79,11 @@ export async function post<T, B = unknown>(url: string, body?: B): Promise<T | u
|
||||||
headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
||||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
if (!res.ok) {
|
||||||
|
const text = await res.text()
|
||||||
|
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
return await res.json()
|
return await res.json()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
|
|
@ -68,7 +96,11 @@ export async function put(url: string, body: Buffer | Uint8Array): Promise<boole
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: body as BodyInit,
|
body: body as BodyInit,
|
||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
if (!res.ok) {
|
||||||
|
const text = await res.text()
|
||||||
|
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
|
|
@ -80,7 +112,11 @@ export async function download(url: string): Promise<Buffer | undefined> {
|
||||||
try {
|
try {
|
||||||
const fullUrl = makeUrl(url)
|
const fullUrl = makeUrl(url)
|
||||||
const res = await fetch(fullUrl)
|
const res = await fetch(fullUrl)
|
||||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
if (!res.ok) {
|
||||||
|
const text = await res.text()
|
||||||
|
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
return Buffer.from(await res.arrayBuffer())
|
return Buffer.from(await res.arrayBuffer())
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
|
|
@ -92,7 +128,11 @@ export async function del(url: string): Promise<boolean> {
|
||||||
const res = await fetch(makeUrl(url), {
|
const res = await fetch(makeUrl(url), {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
if (!res.ok) {
|
||||||
|
const text = await res.text()
|
||||||
|
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,8 @@ export function AppDetail({ app, render }: { app: App, render: () => void }) {
|
||||||
<InfoRow>
|
<InfoRow>
|
||||||
<InfoLabel>URL</InfoLabel>
|
<InfoLabel>URL</InfoLabel>
|
||||||
<InfoValue>
|
<InfoValue>
|
||||||
<Link href={`http://localhost:${app.port}`} target="_blank">
|
<Link href={`${location.protocol}//${location.hostname}:${app.port}`} target="_blank">
|
||||||
http://localhost:{app.port}
|
{location.protocol}//{location.hostname}:{app.port}
|
||||||
</Link>
|
</Link>
|
||||||
</InfoValue>
|
</InfoValue>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,17 @@ export function initToolIframes() {
|
||||||
if (iframes.size === 0) {
|
if (iframes.size === 0) {
|
||||||
const existingIframes = container.querySelectorAll('iframe')
|
const existingIframes = container.querySelectorAll('iframe')
|
||||||
existingIframes.forEach(iframe => {
|
existingIframes.forEach(iframe => {
|
||||||
const match = iframe.src.match(/localhost:(\d+)/)
|
try {
|
||||||
if (match && match[1]) {
|
const url = new URL(iframe.src)
|
||||||
const port = parseInt(match[1], 10)
|
const port = parseInt(url.port, 10)
|
||||||
const toolName = iframe.dataset.toolName
|
const toolName = iframe.dataset.toolName
|
||||||
const appName = iframe.dataset.appName
|
const appName = iframe.dataset.appName
|
||||||
if (toolName) {
|
if (port && toolName) {
|
||||||
const cacheKey = appName ? `${toolName}:${appName}` : toolName
|
const cacheKey = appName ? `${toolName}:${appName}` : toolName
|
||||||
iframes.set(cacheKey, { iframe, port })
|
iframes.set(cacheKey, { iframe, port })
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Invalid URL, skip
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -68,11 +70,13 @@ export function initToolIframes() {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build URL with params
|
// Build URL with params using TOES_URL base
|
||||||
function buildToolUrl(port: number, params: Record<string, string>): string {
|
function buildToolUrl(port: number, params: Record<string, string>): string {
|
||||||
|
const base = new URL(window.location.origin)
|
||||||
|
base.port = String(port)
|
||||||
const searchParams = new URLSearchParams(params)
|
const searchParams = new URLSearchParams(params)
|
||||||
const query = searchParams.toString()
|
const query = searchParams.toString()
|
||||||
return query ? `http://localhost:${port}?${query}` : `http://localhost:${port}`
|
return query ? `${base.origin}?${query}` : base.origin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build cache key from tool name and params
|
// Build cache key from tool name and params
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { allApps, initApps } from '$apps'
|
import { allApps, initApps, TOES_URL } from '$apps'
|
||||||
import appsRouter from './api/apps'
|
import appsRouter from './api/apps'
|
||||||
import syncRouter from './api/sync'
|
import syncRouter from './api/sync'
|
||||||
import { Hype } from '@because/hype'
|
import { Hype } from '@because/hype'
|
||||||
|
|
@ -16,7 +16,9 @@ app.get('/tool/:tool', c => {
|
||||||
return c.text(`Tool "${toolName}" not found or not running`, 404)
|
return c.text(`Tool "${toolName}" not found or not running`, 404)
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams(c.req.query()).toString()
|
const params = new URLSearchParams(c.req.query()).toString()
|
||||||
const url = params ? `http://localhost:${tool.port}?${params}` : `http://localhost:${tool.port}`
|
const base = new URL(TOES_URL)
|
||||||
|
base.port = String(tool.port)
|
||||||
|
const url = params ? `${base.origin}?${params}` : base.origin
|
||||||
return c.redirect(url)
|
return c.redirect(url)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user