From 664861a0fb49eab183137e8c3c189975dda6c08d Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:24:39 -0800 Subject: [PATCH] code app --- apps/code/.npmrc | 1 + apps/code/bun.lock | 43 +++++ apps/code/index.tsx | 1 + apps/code/package.json | 25 +++ apps/code/src/pages/index.tsx | 1 + apps/code/src/server/index.tsx | 327 +++++++++++++++++++++++++++++++++ apps/code/tsconfig.json | 29 +++ 7 files changed, 427 insertions(+) create mode 100644 apps/code/.npmrc create mode 100644 apps/code/bun.lock create mode 100644 apps/code/index.tsx create mode 100644 apps/code/package.json create mode 100644 apps/code/src/pages/index.tsx create mode 100644 apps/code/src/server/index.tsx create mode 100644 apps/code/tsconfig.json diff --git a/apps/code/.npmrc b/apps/code/.npmrc new file mode 100644 index 0000000..6c57d5c --- /dev/null +++ b/apps/code/.npmrc @@ -0,0 +1 @@ +registry=https://npm.nose.space diff --git a/apps/code/bun.lock b/apps/code/bun.lock new file mode 100644 index 0000000..fbb9f3f --- /dev/null +++ b/apps/code/bun.lock @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "code", + "dependencies": { + "@because/forge": "*", + "@because/howl": "*", + "@because/hype": "*", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.9.2", + }, + }, + }, + "packages": { + "@because/forge": ["@because/forge@0.0.1", "https://npm.nose.space/@because/forge/-/forge-0.0.1.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-QS5CK51gcWma91i4uECWe4HPJeNHcE+Af4SQHOcfEovyzOEa7VOTAjei+jIWr2i+abGWqQCEC9wIuFgPgyr2Bg=="], + + "@because/howl": ["@because/howl@0.0.2", "https://npm.nose.space/@because/howl/-/howl-0.0.2.tgz", { "dependencies": { "lucide-static": "^0.555.0" }, "peerDependencies": { "@because/forge": "*", "typescript": "^5" } }, "sha512-Z4okzEa282LKkBk9DQwEUU6FT+PeThfQ6iQAY41LIEjs8B2kfXRZnbWLs7tgpwCfYORxb0RO4Hr7KiyEqnfTvQ=="], + + "@because/hype": ["@because/hype@0.0.1", "https://npm.nose.space/@because/hype/-/hype-0.0.1.tgz", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "sha512-i92DNUXJOwt3J8dN1x8sh7i86blelcTCk8XDpwD839Ic8oe710lkDSVXJ7xYZb/i8YtzGhRg+L6eXDhaRiU2Pw=="], + + "@types/bun": ["@types/bun@1.3.8", "https://npm.nose.space/@types/bun/-/bun-1.3.8.tgz", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + + "@types/node": ["@types/node@25.1.0", "https://npm.nose.space/@types/node/-/node-25.1.0.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="], + + "bun-types": ["bun-types@1.3.8", "https://npm.nose.space/bun-types/-/bun-types-1.3.8.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], + + "hono": ["hono@4.11.7", "https://npm.nose.space/hono/-/hono-4.11.7.tgz", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="], + + "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.16.0", "https://npm.nose.space/undici-types/-/undici-types-7.16.0.tgz", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + } +} diff --git a/apps/code/index.tsx b/apps/code/index.tsx new file mode 100644 index 0000000..9b61e99 --- /dev/null +++ b/apps/code/index.tsx @@ -0,0 +1 @@ +export { default } from './src/server' diff --git a/apps/code/package.json b/apps/code/package.json new file mode 100644 index 0000000..0c83129 --- /dev/null +++ b/apps/code/package.json @@ -0,0 +1,25 @@ +{ + "name": "code", + "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": "*" + } +} diff --git a/apps/code/src/pages/index.tsx b/apps/code/src/pages/index.tsx new file mode 100644 index 0000000..1bf47e3 --- /dev/null +++ b/apps/code/src/pages/index.tsx @@ -0,0 +1 @@ +export default () =>

code

diff --git a/apps/code/src/server/index.tsx b/apps/code/src/server/index.tsx new file mode 100644 index 0000000..79bb312 --- /dev/null +++ b/apps/code/src/server/index.tsx @@ -0,0 +1,327 @@ +import { Hype } from '@because/hype' +import { define, stylesToCSS } from '@because/forge' +import { readdir, stat } from 'fs/promises' +import { readFileSync } from 'fs' +import { join, extname, basename } from 'path' + +const APPS_DIR = process.env.APPS_DIR! + +const app = new Hype({ prettyHTML: false }) + +// Styles +const Container = define('CodeBrowserContainer', { + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + padding: '20px', + maxWidth: '1200px', + margin: '0 auto', +}) + +const Header = define('Header', { + marginBottom: '20px', + paddingBottom: '10px', + borderBottom: '2px solid #333', +}) + +const Title = define('Title', { + margin: 0, + fontSize: '24px', + fontWeight: 'bold', +}) + +const AppName = define('AppName', { + color: '#666', + fontSize: '18px', + marginTop: '5px', +}) + +const FileList = define('FileList', { + listStyle: 'none', + padding: 0, + margin: '20px 0', + border: '1px solid #ddd', + borderRadius: '4px', + overflow: 'hidden', +}) + +const FileItem = define('FileItem', { + padding: '10px 15px', + borderBottom: '1px solid #eee', + states: { + ':last-child': { + borderBottom: 'none', + }, + ':hover': { + backgroundColor: '#f5f5f5', + }, + } +}) + +const FileLink = define('FileLink', { + base: 'a', + textDecoration: 'none', + color: '#0066cc', + display: 'flex', + alignItems: 'center', + gap: '8px', + states: { + ':hover': { + textDecoration: 'underline', + }, + } +}) + +const CodeBlock = define('CodeBlock', { + margin: '20px 0', + border: '1px solid #ddd', + borderRadius: '4px', + overflow: 'auto', + selectors: { + '& pre': { + margin: 0, + padding: '15px', + overflow: 'auto', + whiteSpace: 'pre', + }, + '& pre code': { + whiteSpace: 'pre', + fontFamily: 'monospace', + }, + }, +}) + +const CodeHeader = define('CodeHeader', { + padding: '10px 15px', + backgroundColor: '#f5f5f5', + borderBottom: '1px solid #ddd', + fontWeight: 'bold', + fontSize: '14px', +}) + +const Error = define('Error', { + color: '#d32f2f', + padding: '20px', + backgroundColor: '#ffebee', + borderRadius: '4px', + margin: '20px 0', +}) + +const BackLink = define('BackLink', { + base: 'a', + textDecoration: 'none', + color: '#0066cc', + display: 'inline-flex', + alignItems: 'center', + gap: '5px', + marginBottom: '15px', + states: { + ':hover': { + textDecoration: 'underline', + }, + }, +}) + +app.get('/styles.css', c => c.text(stylesToCSS(), 200, { + 'Content-Type': 'text/css; charset=utf-8', +})) + +async function listFiles(appPath: string, subPath: string = '') { + const fullPath = join(appPath, subPath) + const entries = await readdir(fullPath, { withFileTypes: true }) + + const items = await Promise.all( + entries.map(async entry => { + const itemPath = join(fullPath, entry.name) + const stats = await stat(itemPath) + return { + name: entry.name, + isDirectory: entry.isDirectory(), + path: subPath ? `${subPath}/${entry.name}` : entry.name, + } + }) + ) + + return items.sort((a, b) => { + if (a.isDirectory !== b.isDirectory) { + return a.isDirectory ? -1 : 1 + } + return a.name.localeCompare(b.name) + }) +} + +function escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +function getLanguage(filename: string): string { + const ext = extname(filename).toLowerCase() + const langMap: Record = { + '.js': 'javascript', + '.jsx': 'javascript', + '.ts': 'typescript', + '.tsx': 'typescript', + '.json': 'json', + '.css': 'css', + '.html': 'html', + '.md': 'markdown', + '.sh': 'bash', + '.yml': 'yaml', + '.yaml': 'yaml', + } + return langMap[ext] || 'plaintext' +} + +app.get('/', async c => { + const appName = c.req.query('app') + const filePath = c.req.query('file') || '' + + if (!appName) { + return c.html( + + + + + Code Browser + + + + +
+ Code Browser +
+ Please specify an app name with ?app=<name> +
+ + + ) + } + + const appPath = join(APPS_DIR, appName) + + try { + await stat(appPath) + } catch { + return c.html( + + + + + Code Browser + + + + +
+ Code Browser +
+ App "{appName}" not found +
+ + + ) + } + + const fullPath = join(appPath, filePath) + let stats + + try { + stats = await stat(fullPath) + } catch { + return c.html( + + + + + Code Browser + + + + +
+ Code Browser +
+ Path "{filePath}" not found in app "{appName}" +
+ + + ) + } + + if (stats.isFile()) { + const content = readFileSync(fullPath, 'utf-8') + const language = getLanguage(basename(fullPath)) + const parentPath = filePath.split('/').slice(0, -1).join('/') + + return c.html( + + + + + {`${appName}/${filePath}`} + + + + + + +
+ Code Browser + {appName}/{filePath} +
+ + ⬅️ Back + + + {basename(fullPath)} +
{content}
+
+
+