Add test123 app and support tunnelUrl in Urls
This commit is contained in:
parent
526678e87a
commit
141622f86f
39
apps/test123/20260227-072646/.gitignore
vendored
Normal file
39
apps/test123/20260227-072646/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# dev data
|
||||||
|
data/
|
||||||
|
|
||||||
|
# honk honk
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# output
|
||||||
|
dist/
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
1
apps/test123/20260227-072646/.npmrc
Normal file
1
apps/test123/20260227-072646/.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
registry=https://npm.nose.space
|
||||||
307
apps/test123/20260227-072646/CLAUDE.md
Normal file
307
apps/test123/20260227-072646/CLAUDE.md
Normal file
|
|
@ -0,0 +1,307 @@
|
||||||
|
# Toes - Guide to Writing Apps
|
||||||
|
|
||||||
|
Toes manages and runs web apps, each on its own port.
|
||||||
|
|
||||||
|
Apps are server-rendered TypeScript using **Hype** (wraps Hono) and **Forge** (CSS-in-JS).
|
||||||
|
|
||||||
|
Runtime is **Bun**.
|
||||||
|
|
||||||
|
## Required Components
|
||||||
|
|
||||||
|
Every toes app/tool must have:
|
||||||
|
|
||||||
|
1. **`.npmrc`** pointing to `registry=https://npm.nose.space` (the private registry for `@because/*` packages)
|
||||||
|
2. **`package.json`** with a `scripts.toes` entry (this is how toes discovers and runs apps)
|
||||||
|
3. **HTTP `GET /ok`** returning 200 (health check endpoint — toes polls this every 30s and restarts unresponsive apps)
|
||||||
|
|
||||||
|
## App vs Tool
|
||||||
|
|
||||||
|
An **app** shows in the sidebar and opens in its own browser tab.
|
||||||
|
|
||||||
|
A **tool** renders as a tab inside the dashboard (in an iframe). It receives `?app=<name>` to know the selected app. The only code difference is `"toes": { "tool": true }` in package.json and some extra imports from `@because/toes`.
|
||||||
|
|
||||||
|
## Required Files
|
||||||
|
|
||||||
|
Every app needs `.npmrc`, `tsconfig.json`, `package.json`, and `index.tsx`.
|
||||||
|
|
||||||
|
**.npmrc** -- always this exact content:
|
||||||
|
```
|
||||||
|
registry=https://npm.nose.space
|
||||||
|
```
|
||||||
|
|
||||||
|
**tsconfig.json** -- use exactly, do not improvise:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "hono/jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"$*": ["src/server/*"],
|
||||||
|
"#*": ["src/client/*"],
|
||||||
|
"@*": ["src/shared/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**package.json** for an app:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-app",
|
||||||
|
"private": true,
|
||||||
|
"module": "index.tsx",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": { "toes": "bun run --watch index.tsx" },
|
||||||
|
"toes": { "icon": "🖥️" },
|
||||||
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
|
"@because/hype": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": { "@types/bun": "latest" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For a **tool**, add `@because/toes` to dependencies and set `"tool": true` (or a string for a custom tab label like `"tool": ".env"`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"toes": { "icon": "🔧", "tool": true },
|
||||||
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
|
"@because/hype": "*",
|
||||||
|
"@because/toes": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hype
|
||||||
|
|
||||||
|
Hype wraps Hono. It adds `app.defaults` (the Bun server export), `app.sse()` for server-sent events, and `Hype.router()` for sub-routers. Everything else is standard Hono.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Hype } from '@because/hype'
|
||||||
|
const app = new Hype()
|
||||||
|
|
||||||
|
app.get('/', c => c.html(<h1>Hello</h1>))
|
||||||
|
app.get('/ok', c => c.text('ok')) // Health check -- required
|
||||||
|
|
||||||
|
export default app.defaults
|
||||||
|
```
|
||||||
|
|
||||||
|
Constructor options: `prettyHTML` (default true, tools should set false), `layout` (default true), `logging` (default true).
|
||||||
|
|
||||||
|
**SSE** -- the one non-Hono addition:
|
||||||
|
```tsx
|
||||||
|
app.sse('/stream', (send, c) => {
|
||||||
|
send({ hello: 'world' })
|
||||||
|
const interval = setInterval(() => send({ time: Date.now() }), 1000)
|
||||||
|
return () => clearInterval(interval) // cleanup on disconnect
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sub-routers:**
|
||||||
|
```tsx
|
||||||
|
const api = Hype.router()
|
||||||
|
api.get('/items', c => c.json([]))
|
||||||
|
app.route('/api', api) // mounts at /api/items
|
||||||
|
```
|
||||||
|
|
||||||
|
## Forge
|
||||||
|
|
||||||
|
Forge creates styled JSX components via `define()`. Properties use camelCase CSS. Numbers auto-convert to `px` (except `flex`, `opacity`, `zIndex`, `fontWeight`).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { define, stylesToCSS } from '@because/forge'
|
||||||
|
|
||||||
|
const Box = define('Box', {
|
||||||
|
padding: 20,
|
||||||
|
borderRadius: '6px',
|
||||||
|
})
|
||||||
|
// <Box>content</Box> renders <div class="Box">content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**`base`** -- set the HTML element (default `div`):
|
||||||
|
```tsx
|
||||||
|
const Button = define('Button', { base: 'button', padding: '8px 16px' })
|
||||||
|
const Link = define('Link', { base: 'a', textDecoration: 'none' })
|
||||||
|
```
|
||||||
|
|
||||||
|
**`states`** -- pseudo-classes:
|
||||||
|
```tsx
|
||||||
|
const Item = define('Item', {
|
||||||
|
padding: 12,
|
||||||
|
states: {
|
||||||
|
':hover': { backgroundColor: '#eee' },
|
||||||
|
':last-child': { borderBottom: 'none' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**`selectors`** -- nested CSS (`&` = the component):
|
||||||
|
```tsx
|
||||||
|
const List = define('List', {
|
||||||
|
selectors: {
|
||||||
|
'& > li:last-child': { borderBottom: 'none' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**`variants`** -- conditional styles via props:
|
||||||
|
```tsx
|
||||||
|
const Button = define('Button', {
|
||||||
|
base: 'button',
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
primary: { backgroundColor: '#2563eb', color: 'white' },
|
||||||
|
danger: { backgroundColor: '#dc2626', color: 'white' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// <Button variant="primary">Save</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Serving CSS** -- apps serve `stylesToCSS()` from a route. Tools prepend `baseStyles`:
|
||||||
|
```tsx
|
||||||
|
app.get('/styles.css', c =>
|
||||||
|
c.text(baseStyles + stylesToCSS(), 200, { 'Content-Type': 'text/css; charset=utf-8' })
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme Tokens
|
||||||
|
|
||||||
|
Tools import `theme` from `@because/toes/tools`. It returns CSS variables that resolve per light/dark mode.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { baseStyles, ToolScript, theme } from '@because/toes/tools'
|
||||||
|
|
||||||
|
const Container = define('Container', {
|
||||||
|
color: theme('colors-text'),
|
||||||
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Available tokens:
|
||||||
|
|
||||||
|
| Token | Use for |
|
||||||
|
|-------|---------|
|
||||||
|
| `colors-bg`, `colors-bgSubtle`, `colors-bgElement`, `colors-bgHover` | Backgrounds |
|
||||||
|
| `colors-text`, `colors-textMuted`, `colors-textFaint` | Text |
|
||||||
|
| `colors-border` | Borders |
|
||||||
|
| `colors-link` | Links |
|
||||||
|
| `colors-primary`, `colors-primaryText` | Primary actions |
|
||||||
|
| `colors-error`, `colors-dangerBorder`, `colors-dangerText` | Errors/danger |
|
||||||
|
| `colors-success`, `colors-successBg` | Success states |
|
||||||
|
| `colors-statusRunning`, `colors-statusStopped` | Status indicators |
|
||||||
|
| `fonts-sans`, `fonts-mono` | Font stacks |
|
||||||
|
| `spacing-xs` (4), `spacing-sm` (8), `spacing-md` (12), `spacing-lg` (16), `spacing-xl` (24) | Spacing (px) |
|
||||||
|
| `radius-md` (6px) | Border radius |
|
||||||
|
|
||||||
|
## Writing a Tool
|
||||||
|
|
||||||
|
Tools need three extra things vs apps:
|
||||||
|
|
||||||
|
1. `<ToolScript />` in `<body>` (handles dark mode + iframe height communication)
|
||||||
|
2. `baseStyles` prepended to CSS output
|
||||||
|
3. Handle the `?app=` query param
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Hype } from '@because/hype'
|
||||||
|
import { define, stylesToCSS } from '@because/forge'
|
||||||
|
import { baseStyles, ToolScript, theme } from '@because/toes/tools'
|
||||||
|
import type { Child } from 'hono/jsx'
|
||||||
|
|
||||||
|
const APPS_DIR = process.env.APPS_DIR!
|
||||||
|
const app = new Hype({ prettyHTML: false })
|
||||||
|
|
||||||
|
const Container = define('Container', {
|
||||||
|
fontFamily: theme('fonts-sans'),
|
||||||
|
padding: '20px',
|
||||||
|
paddingTop: 0,
|
||||||
|
maxWidth: '800px',
|
||||||
|
margin: '0 auto',
|
||||||
|
color: theme('colors-text'),
|
||||||
|
})
|
||||||
|
|
||||||
|
function Layout({ title, children }: { title: string; children: Child }) {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{title}</title>
|
||||||
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ToolScript />
|
||||||
|
<Container>{children}</Container>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/ok', c => c.text('ok'))
|
||||||
|
app.get('/styles.css', c =>
|
||||||
|
c.text(baseStyles + stylesToCSS(), 200, { 'Content-Type': 'text/css; charset=utf-8' })
|
||||||
|
)
|
||||||
|
|
||||||
|
app.get('/', async c => {
|
||||||
|
const appName = c.req.query('app')
|
||||||
|
if (!appName) return c.html(<Layout title="My Tool"><p>No app selected</p></Layout>)
|
||||||
|
// ... tool logic using join(APPS_DIR, appName, 'current') for file paths
|
||||||
|
return c.html(<Layout title="My Tool">...</Layout>)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default app.defaults
|
||||||
|
```
|
||||||
|
|
||||||
|
**Environment variables** available to tools: `APPS_DIR`, `TOES_URL` (base URL of Toes server), `PORT`, `TOES_DIR`.
|
||||||
|
|
||||||
|
**Accessing app files:** always use `join(APPS_DIR, appName, 'current')`.
|
||||||
|
|
||||||
|
**Calling the Toes API:** `fetch(\`${TOES_URL}/api/apps\`)`, `fetch(\`${TOES_URL}/api/apps/${name}\`)`.
|
||||||
|
|
||||||
|
**Linking between tools:** `<a href={\`${TOES_URL}/tool/code?app=${appName}\`}>View Code</a>`.
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
|
||||||
|
**Fire-and-forget with polling** -- for long-running ops, don't await in POST. Use `<meta http-equiv="refresh" content="2" />` to poll while running.
|
||||||
|
|
||||||
|
**Inline client JS** -- use `<script dangerouslySetInnerHTML={{ __html: script }} />`.
|
||||||
|
|
||||||
|
**Data persistence** -- use filesystem. `DATA_DIR` env var points to a per-app data directory.
|
||||||
|
|
||||||
|
## Cron Jobs
|
||||||
|
|
||||||
|
Place `.ts` files in an app's `cron/` directory:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const schedule = "day"
|
||||||
|
export default async function() {
|
||||||
|
console.log("Running at", new Date().toISOString())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Valid schedules: `"1 minute"`, `"5 minutes"`, `"15 minutes"`, `"30 minutes"`, `"hour"`, `"noon"`, `"midnight"`, `"day"`, `"week"`, `"sunday"` through `"saturday"`.
|
||||||
|
|
||||||
|
## Coding Guidelines
|
||||||
|
|
||||||
|
TS file organization order: imports, re-exports, const/lets, enums, interfaces, types, classes, functions, module init. Within each section, exports first (alphabetical), then non-exports (alphabetical).
|
||||||
|
|
||||||
|
Single-line functions: `const fn = () => {}`. Multi-line: `function name() {}`.
|
||||||
43
apps/test123/20260227-072646/bun.lock
Normal file
43
apps/test123/20260227-072646/bun.lock
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "test123",
|
||||||
|
"dependencies": {
|
||||||
|
"@because/forge": "*",
|
||||||
|
"@because/howl": "*",
|
||||||
|
"@because/hype": "*",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.9.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@because/forge": ["@because/forge@0.0.3", "https://npm.nose.space/@because/forge/-/forge-0.0.3.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-3V2be2vkkW1qZH6WSurfYBgyVjot/GcyOt5LfIVyEYQ5cAq6bWbtxsXs5CK/sT8OqbvCu3VHc2k2OXOC6Q3feg=="],
|
||||||
|
|
||||||
|
"@because/howl": ["@because/howl@0.0.3", "https://npm.nose.space/@because/howl/-/howl-0.0.3.tgz", { "dependencies": { "lucide-static": "^0.555.0" }, "peerDependencies": { "@because/forge": "*", "typescript": "^5" } }, "sha512-D4WI1OmXpqI07Gpfqs0UbZ85qaF9ZapUVbKJqiN4FVZtpNAZnbyTfuKxy8qH0ratcoGRGSoQFis0Z3XvWlzpsw=="],
|
||||||
|
|
||||||
|
"@because/hype": ["@because/hype@0.0.6", "https://npm.nose.space/@because/hype/-/hype-0.0.6.tgz", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "sha512-WSRPNoeTBR3nRcPTqfbu6+FUaNenCo/sN/CB2Ism7oiJwTap1i+1AlWPa+MF1eMQlNd2AYRlA3AAu6F52j6/fA=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.3.9", "https://npm.nose.space/@types/bun/-/bun-1.3.9.tgz", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@25.3.2", "https://npm.nose.space/@types/node/-/node-25.3.2.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.3.9", "https://npm.nose.space/bun-types/-/bun-types-1.3.9.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||||
|
|
||||||
|
"hono": ["hono@4.12.3", "https://npm.nose.space/hono/-/hono-4.12.3.tgz", {}, "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg=="],
|
||||||
|
|
||||||
|
"kleur": ["kleur@4.1.5", "https://npm.nose.space/kleur/-/kleur-4.1.5.tgz", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
|
"lucide-static": ["lucide-static@0.555.0", "https://npm.nose.space/lucide-static/-/lucide-static-0.555.0.tgz", {}, "sha512-FMMaYYsEYsUA6xlEzIMoKEV3oGnxIIvAN+AtLmYXvlTJptJTveJjVBQwvtA/zZLrD6KLEu89G95dQYlhivw5jQ=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.3", "https://npm.nose.space/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.18.2", "https://npm.nose.space/undici-types/-/undici-types-7.18.2.tgz", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
1
apps/test123/20260227-072646/index.tsx
Normal file
1
apps/test123/20260227-072646/index.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './src/server'
|
||||||
25
apps/test123/20260227-072646/package.json
Normal file
25
apps/test123/20260227-072646/package.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "test123",
|
||||||
|
"module": "index.tsx",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"toes": "bun run --watch index.tsx",
|
||||||
|
"start": "bun toes",
|
||||||
|
"dev": "bun run --hot index.tsx"
|
||||||
|
},
|
||||||
|
"toes": {
|
||||||
|
"icon": "🖥️"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@because/hype": "*",
|
||||||
|
"@because/forge": "*",
|
||||||
|
"@because/howl": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
apps/test123/20260227-072646/src/pages/index.tsx
Normal file
1
apps/test123/20260227-072646/src/pages/index.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export default () => <h1>test123</h1>
|
||||||
7
apps/test123/20260227-072646/src/server/index.tsx
Normal file
7
apps/test123/20260227-072646/src/server/index.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Hype } from '@because/hype'
|
||||||
|
|
||||||
|
const app = new Hype()
|
||||||
|
|
||||||
|
app.get('/ok', c => c.text('ok'))
|
||||||
|
|
||||||
|
export default app.defaults
|
||||||
29
apps/test123/20260227-072646/tsconfig.json
Normal file
29
apps/test123/20260227-072646/tsconfig.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "hono/jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"$*": ["src/server/*"],
|
||||||
|
"#*": ["src/client/*"],
|
||||||
|
"@*": ["src/shared/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
TileGrid,
|
TileGrid,
|
||||||
TileIcon,
|
TileIcon,
|
||||||
TileName,
|
TileName,
|
||||||
TilePort,
|
|
||||||
TileStatus,
|
TileStatus,
|
||||||
} from '../styles'
|
} from '../styles'
|
||||||
|
|
||||||
|
|
@ -21,7 +20,7 @@ export function Urls({ render }: { render: () => void }) {
|
||||||
return (
|
return (
|
||||||
<TileGrid narrow={isNarrow || undefined}>
|
<TileGrid narrow={isNarrow || undefined}>
|
||||||
{nonTools.map(app => {
|
{nonTools.map(app => {
|
||||||
const url = buildAppUrl(app.name, location.origin)
|
const url = app.tunnelUrl || buildAppUrl(app.name, location.origin)
|
||||||
const running = app.state === 'running'
|
const running = app.state === 'running'
|
||||||
const appPage = `/app/${app.name}`
|
const appPage = `/app/${app.name}`
|
||||||
|
|
||||||
|
|
@ -41,7 +40,6 @@ export function Urls({ render }: { render: () => void }) {
|
||||||
<TileStatus state={app.state} onClick={openAppPage} />
|
<TileStatus state={app.state} onClick={openAppPage} />
|
||||||
<TileIcon>{app.icon}</TileIcon>
|
<TileIcon>{app.icon}</TileIcon>
|
||||||
<TileName>{app.name}</TileName>
|
<TileName>{app.name}</TileName>
|
||||||
<TilePort>{app.port ? `:${app.port}` : '\u2014'}</TilePort>
|
|
||||||
</Tile>
|
</Tile>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user