Initial commit from create-turbo

This commit is contained in:
TZGyn
2026-05-09 15:27:01 +08:00
commit 22064d4bc4
482 changed files with 15482 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
import fs from 'node:fs'
import { BunPlugin } from 'bun'
import path from 'node:path'
export function globSync(
globPattern: string,
globOptions: { cwd?: string },
) {
return Array.from(new Bun.Glob(globPattern).scanSync(globOptions))
}
const fsUsageMock = /* ts */ `{
default: {
existsSync() {
return true;
},
statSync() {
return {
isDirectory() {
return true;
}
}
}
}
}`
const fileSourcesString = (files: string[], directory: string) => `
const fileSources = {
${files
.map(
(file) => /* ts */ `
"${file.replaceAll('\\', '/')}": await import("${path
.resolve(directory, file)
.replace(/\\/gi, '\\\\')}"),`,
)
.join('\n')}
}
`
const filerouter = () => {
return {
name: 'file-router',
setup: (build) => {
const directory = './src/routes'
build.onLoad(
{ filter: /(.*)file-router(\/|\\)(.*).(ts)/i },
async (args) => {
const pattern = '**/*.{ts,tsx,js,jsx,mjs,cjs}'
let content = String(await fs.promises.readFile(args.path))
const files = globSync(pattern, { cwd: directory })
content = content.replace('watch()', '')
content = content.replace(
'delete require.cache[require.resolve(pathToFileURL(fullPath).href)]',
'',
)
content = content.replace(
'const files = globSync(globPattern, globOptions)',
/* ts */ `const files = [${files
.map((file) => `"${file.replaceAll('\\', '/')}"`)
.join(',')}];`,
)
content = content.replace(
`import fs from 'node:fs'`,
`var { default: fs} = ${fsUsageMock}`,
)
content = content.replace(
'// insert imports',
fileSourcesString(files, directory),
)
content = content.replace(
/const file = (.*)/i,
'const file = fileSources[filepath]',
)
return { contents: content }
},
)
},
} satisfies BunPlugin
}
await Bun.build({
entrypoints: ['file-router/index.ts'],
target: 'bun',
outdir: 'build',
sourcemap: true,
plugins: [filerouter()],
compile: true,
}).then(console.log)
@@ -0,0 +1,85 @@
import { getParamsFromFilePath } from '../utils/get-params-from-file-path'
import { transformToUrl } from '../utils/transform-to-url'
type Stats = {
isFile(): boolean
isDirectory(): boolean
isBlockDevice(): boolean
isCharacterDevice(): boolean
isSymbolicLink(): boolean
isFIFO(): boolean
isSocket(): boolean
dev: number
ino: number
mode: number
nlink: number
uid: number
gid: number
rdev: number
size: number
blksize: number
blocks: number
atimeMs: number
mtimeMs: number
ctimeMs: number
birthtimeMs: number
atime: Date
mtime: Date
ctime: Date
birthtime: Date
}
const generateTemplate = ({ params }: { params?: string }) => {
let config = ''
if (params) {
config += `{ params: ${params} }`
}
if (config.length > 0) {
config = ',' + config
}
return `import Elysia from 'elysia'
import { betterAuth } from '$lib/plugin/better-auth'
export const route = new Elysia()
.use(betterAuth)
.get('/', async () => {
return 'hello'
}${config})
`
}
export const onFileCreate = async (path: string, stats?: Stats) => {
const file = Bun.file(path)
const params = getParamsFromFilePath(path)
const paramsTemplate = getParamsTemplate(params)
let template: string
template = generateTemplate({ params: paramsTemplate })
if ((await file.text()) === '') {
await file.write(template)
console.log(await Bun.$`bunx prettier --write ${path}`)
}
}
const getParamsTemplate = (params: string[]) => {
let result = ''
if (params.length > 0) {
result += `t.Object({`
params.map((param) => {
result += `"${param}": t.String(),`
})
result += `})`
}
return result
}
+183
View File
@@ -0,0 +1,183 @@
import { createApp } from '$routes/index'
import { Elysia } from 'elysia'
import path from 'node:path'
import readline from 'node:readline'
import { pathToFileURL } from 'bun'
import chokidar from 'chokidar'
import { Cron } from 'croner'
import { onFileCreate } from './hooks/on-file-create'
import { transformToUrl } from './utils/transform-to-url'
export function addRelativeIfNotDot(path: string) {
if (path.charAt(0) !== '.') return `./${path}`
return path
}
export function globSync(
globPattern: string,
globOptions: { cwd?: string },
) {
return Array.from(new Bun.Glob(globPattern).scanSync(globOptions))
}
export function getPath(dir: string) {
if (path.isAbsolute(dir)) return dir
if (path.isAbsolute(process.argv[1]))
return path.join(process.argv[1], '..', dir)
return path.join(process.cwd(), process.argv[1], '..', dir)
}
const fileRouter = async () => {
const directoryPath = getPath('../src/routes')
const globPattern = '**/*.{ts,tsx,js,jsx,mjs,cjs}'
const globOptions = { cwd: directoryPath }
const files = globSync(globPattern, globOptions)
const plugin = new Elysia({ name: 'file-router' })
// insert imports
const paths: [path: string, exportName: string][] = []
for (const filepath of files) {
const fullPath = path.join(directoryPath, filepath)
const file = await import(pathToFileURL(fullPath).href)
// prettier-ignore
delete require.cache[require.resolve(pathToFileURL(fullPath).href)]
const url = transformToUrl(filepath)
if (!file['route']) continue
const importedValue = file['route']
plugin.group(url, {}, (app) => app.use(importedValue))
paths.push([fullPath.replace(directoryPath, ''), 'route'])
}
const outputAbsolutePath = './src/route-types.ts'
const imports: string[] = paths.map(
([filePath, exportName], index) => {
const importPath = filePath.replace(/\.(ts|tsx)$/, '')
return `import type ${
exportName === 'default'
? `Route${index}`
: `{ ${exportName} as Route${index} }`
} from "${addRelativeIfNotDot(
path
.relative(
path.dirname(outputAbsolutePath),
path.join(directoryPath, importPath),
)
.replaceAll('\\', '/'),
)}";`
},
)
const input = [
`import type { ElysiaWithBaseUrl } from "../file-router/types.ts";`,
imports.join('\n'),
'',
`export type Route = ${paths
.map(
([x], index) =>
`ElysiaWithBaseUrl<"${
transformToUrl(x) || '/'
}", typeof Route${index}>`,
)
.join('\n\t\t\t& ')}`,
].join('\n')
await Bun.file(outputAbsolutePath).write(input)
return plugin
}
function clearScreen() {
const repeatCount = process.stdout.rows - 2
const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : ''
console.log(blank)
readline.cursorTo(process.stdout, 0, 0)
readline.clearScreenDown(process.stdout)
}
let app = createApp()
app.use(await fileRouter())
const server = Bun.serve({
fetch: app.fetch,
port: Bun.env.PORT ?? 3000,
})
const reloadServer = async () => {
if ('cron' in app.store) {
// @ts-ignore
const cron = app.store.cron as Record<string, Cron>
for (const [key, value] of Object.entries(cron)) {
// @ts-ignore
app.store.cron[key].stop()
}
}
app = createApp()
app.use(await fileRouter())
server.reload({ fetch: app.fetch })
clearScreen()
console.log(
`🦊 Elysia is running at http://${server.hostname}:${server.port}`,
)
}
const watch = () => {
chokidar
.watch('./src/routes', { persistent: true })
.on('all', async (event, path) => {
console.log(event, path)
for (const path of Object.keys(require.cache)) {
delete require.cache[path]
}
try {
await reloadServer()
} catch (error) {
console.error(error)
}
})
.on('add', async (path, stats) => {
console.log(path, stats)
try {
await onFileCreate(path, stats)
} catch (error) {
console.error(error)
}
})
chokidar
.watch('./lib', { persistent: true })
.on('all', async (event, path) => {
console.log(event, path)
for (const path of Object.keys(require.cache)) {
delete require.cache[path]
}
try {
await reloadServer()
} catch (error) {
console.error(error)
}
})
}
watch()
+65
View File
@@ -0,0 +1,65 @@
import type Elysia from 'elysia'
import type { AnyElysia, RouteBase, RouteSchema } from 'elysia'
type PathToObject<
Path extends string,
Type extends RouteBase,
> = Path extends `${infer Head}/${infer Rest}`
? Head extends ''
? PathToObject<Rest, Type>
: { [K in Head]: PathToObject<Rest, Type> }
: { [K in Path]: Type }
namespace ElysiaMatch {
export type RouteEnd = Record<string, RouteSchema>
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export type Fx = (...args: any[]) => AnyElysia
export type All = AnyElysia | Fx
export type Extract<T extends All> = T extends Fx
? ReturnType<T>
: T
}
type FlattenIndexRoutes<T> = T extends object
? {
[K in keyof T as K extends 'index'
? T[K] extends ElysiaMatch.RouteEnd
? never
: K
: K]: FlattenIndexRoutes<T[K]>
} & (T extends { index: infer I }
? I extends ElysiaMatch.RouteEnd
? FlattenIndexRoutes<I>
: T
: T)
: T
export type ElysiaWithBaseUrl<
BaseUrl extends string,
ElysiaType extends ElysiaMatch.All,
> =
ElysiaMatch.Extract<ElysiaType> extends Elysia<
infer BasePath,
// infer Scoped,
infer Singleton,
infer Definitions,
infer Metadata,
infer Routes,
infer Ephemeral,
infer Volatile
>
? Elysia<
BasePath,
// Scoped,
Singleton,
Definitions,
Metadata,
FlattenIndexRoutes<PathToObject<BaseUrl, Routes>>,
Ephemeral,
Volatile
>
: never
export type SoftString<T extends string> = T | (string & {})
@@ -0,0 +1,9 @@
export const getParamsFromFilePath = (path: string) => {
const params = path.match(/\[(.*?)\]/gu) || []
return params.map((param) => {
return param.replaceAll(
/\[(.*?)\]/gu,
(_: string, match: string) => `${match}`,
)
})
}
@@ -0,0 +1,30 @@
// https://github.com/wobsoriano/elysia-autoroutes/blob/main/src/utils/transformPathToUrl.ts#L4C31-L4C31
export function transformToUrl(path: string) {
return (
path
// Clean the url extensions
.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/u, '')
// Fix windows slashes
.replaceAll('\\', '/')
// Handle wild card based routes - users/[...id]/profile.ts -> users/*/profile
.replaceAll(/\[\.\.\..*\]/gu, '*')
// Handle generic square bracket based routes - users/[id]/index.ts -> users/:id
.replaceAll(
/\[(.*?)\]/gu,
(_: string, match: string) => `:${match}`,
)
.replace(/\/?\((.*)\)/, '')
// Handle the case when multiple parameters are present in one file
// users / [id] - [name].ts to users /: id -:name and users / [id] - [name] / [age].ts to users /: id -: name /: age
.replaceAll(']-[', '-:')
.replaceAll(']/', '/')
.replaceAll(/\[|\]/gu, '')
// remove potential route methods in file name
.replace(
/(\.(?<method>connect|delete|get|head|options|patch|post|put|trace))?(\.(?<env>dev|prod|prerender))?$/,
'',
)
// remove index from end of path
.replace(/\/?index$/, '')
)
}