create-taserjs 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +22 -0
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/addons/drizzle/index.cjs +124 -0
- package/dist/cjs/addons/drizzle/index.cjs.map +1 -0
- package/dist/cjs/addons/drizzle/index.d.cts +2 -0
- package/dist/cjs/addons/kysely/index.cjs +96 -0
- package/dist/cjs/addons/kysely/index.cjs.map +1 -0
- package/dist/cjs/addons/kysely/index.d.cts +2 -0
- package/dist/cjs/addons/pino/index.cjs +30 -0
- package/dist/cjs/addons/pino/index.cjs.map +1 -0
- package/dist/cjs/addons/pino/index.d.cts +2 -0
- package/dist/cjs/addons/prisma/index.cjs +66 -0
- package/dist/cjs/addons/prisma/index.cjs.map +1 -0
- package/dist/cjs/addons/prisma/index.d.cts +2 -0
- package/dist/cjs/addons/registry.cjs +52 -0
- package/dist/cjs/addons/registry.cjs.map +1 -0
- package/dist/cjs/addons/registry.d.cts +5 -0
- package/dist/cjs/addons/types.d.cts +16 -0
- package/dist/cjs/addons/winston/index.cjs +34 -0
- package/dist/cjs/addons/winston/index.cjs.map +1 -0
- package/dist/cjs/addons/winston/index.d.cts +2 -0
- package/dist/cjs/cli.cjs +56 -0
- package/dist/cjs/cli.cjs.map +1 -0
- package/dist/cjs/cli.d.cts +2 -0
- package/dist/cjs/commands/create.cjs +243 -0
- package/dist/cjs/commands/create.cjs.map +1 -0
- package/dist/cjs/commands/create.d.cts +11 -0
- package/dist/cjs/core/json-output.cjs +16 -0
- package/dist/cjs/core/json-output.cjs.map +1 -0
- package/dist/cjs/core/json-output.d.cts +4 -0
- package/dist/cjs/core/package-manager.cjs +64 -0
- package/dist/cjs/core/package-manager.cjs.map +1 -0
- package/dist/cjs/core/package-manager.d.cts +7 -0
- package/dist/cjs/core/parse-options.cjs +59 -0
- package/dist/cjs/core/parse-options.cjs.map +1 -0
- package/dist/cjs/core/parse-options.d.cts +18 -0
- package/dist/cjs/core/project-config.cjs +18 -0
- package/dist/cjs/core/project-config.cjs.map +1 -0
- package/dist/cjs/core/project-config.d.cts +2 -0
- package/dist/cjs/core/resolve-packages.cjs +52 -0
- package/dist/cjs/core/resolve-packages.cjs.map +1 -0
- package/dist/cjs/core/resolve-packages.d.cts +5 -0
- package/dist/cjs/core/scaffold-engine.cjs +73 -0
- package/dist/cjs/core/scaffold-engine.cjs.map +1 -0
- package/dist/cjs/core/scaffold-engine.d.cts +2 -0
- package/dist/cjs/core/types.cjs +27 -0
- package/dist/cjs/core/types.cjs.map +1 -0
- package/dist/cjs/core/types.d.cts +37 -0
- package/dist/cjs/core/validate-project-name.cjs +45 -0
- package/dist/cjs/core/validate-project-name.cjs.map +1 -0
- package/dist/cjs/core/validate-project-name.d.cts +1 -0
- package/dist/cjs/frameworks/index.cjs +106 -0
- package/dist/cjs/frameworks/index.cjs.map +1 -0
- package/dist/cjs/frameworks/index.d.cts +3 -0
- package/dist/cjs/scaffold.d.cts +3 -0
- package/dist/cjs/templates/base.cjs +186 -0
- package/dist/cjs/templates/base.cjs.map +1 -0
- package/dist/cjs/templates/base.d.cts +12 -0
- package/dist/cjs/types.d.cts +1 -0
- package/dist/esm/addons/drizzle/index.d.ts +2 -0
- package/dist/esm/addons/drizzle/index.js +124 -0
- package/dist/esm/addons/drizzle/index.js.map +1 -0
- package/dist/esm/addons/kysely/index.d.ts +2 -0
- package/dist/esm/addons/kysely/index.js +96 -0
- package/dist/esm/addons/kysely/index.js.map +1 -0
- package/dist/esm/addons/pino/index.d.ts +2 -0
- package/dist/esm/addons/pino/index.js +30 -0
- package/dist/esm/addons/pino/index.js.map +1 -0
- package/dist/esm/addons/prisma/index.d.ts +2 -0
- package/dist/esm/addons/prisma/index.js +66 -0
- package/dist/esm/addons/prisma/index.js.map +1 -0
- package/dist/esm/addons/registry.d.ts +5 -0
- package/dist/esm/addons/registry.js +50 -0
- package/dist/esm/addons/registry.js.map +1 -0
- package/dist/esm/addons/types.d.ts +16 -0
- package/dist/esm/addons/winston/index.d.ts +2 -0
- package/dist/esm/addons/winston/index.js +34 -0
- package/dist/esm/addons/winston/index.js.map +1 -0
- package/dist/esm/cli.d.ts +2 -0
- package/dist/esm/cli.js +53 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/commands/create.d.ts +11 -0
- package/dist/esm/commands/create.js +238 -0
- package/dist/esm/commands/create.js.map +1 -0
- package/dist/esm/core/json-output.d.ts +4 -0
- package/dist/esm/core/json-output.js +14 -0
- package/dist/esm/core/json-output.js.map +1 -0
- package/dist/esm/core/package-manager.d.ts +7 -0
- package/dist/esm/core/package-manager.js +62 -0
- package/dist/esm/core/package-manager.js.map +1 -0
- package/dist/esm/core/parse-options.d.ts +18 -0
- package/dist/esm/core/parse-options.js +56 -0
- package/dist/esm/core/parse-options.js.map +1 -0
- package/dist/esm/core/project-config.d.ts +2 -0
- package/dist/esm/core/project-config.js +16 -0
- package/dist/esm/core/project-config.js.map +1 -0
- package/dist/esm/core/resolve-packages.d.ts +5 -0
- package/dist/esm/core/resolve-packages.js +52 -0
- package/dist/esm/core/resolve-packages.js.map +1 -0
- package/dist/esm/core/scaffold-engine.d.ts +2 -0
- package/dist/esm/core/scaffold-engine.js +71 -0
- package/dist/esm/core/scaffold-engine.js.map +1 -0
- package/dist/esm/core/types.d.ts +37 -0
- package/dist/esm/core/types.js +23 -0
- package/dist/esm/core/types.js.map +1 -0
- package/dist/esm/core/validate-project-name.d.ts +1 -0
- package/dist/esm/core/validate-project-name.js +43 -0
- package/dist/esm/core/validate-project-name.js.map +1 -0
- package/dist/esm/frameworks/index.d.ts +3 -0
- package/dist/esm/frameworks/index.js +105 -0
- package/dist/esm/frameworks/index.js.map +1 -0
- package/dist/esm/scaffold.d.ts +3 -0
- package/dist/esm/templates/base.d.ts +12 -0
- package/dist/esm/templates/base.js +178 -0
- package/dist/esm/templates/base.js.map +1 -0
- package/dist/esm/types.d.ts +1 -0
- package/package.json +45 -0
- package/src/addons/drizzle/index.ts +145 -0
- package/src/addons/kysely/index.ts +111 -0
- package/src/addons/pino/index.ts +30 -0
- package/src/addons/prisma/index.ts +81 -0
- package/src/addons/registry.ts +73 -0
- package/src/addons/types.ts +19 -0
- package/src/addons/winston/index.ts +34 -0
- package/src/cli.ts +57 -0
- package/src/commands/create.ts +253 -0
- package/src/core/json-output.ts +13 -0
- package/src/core/package-manager.ts +68 -0
- package/src/core/parse-options.ts +97 -0
- package/src/core/project-config.ts +27 -0
- package/src/core/resolve-packages.ts +62 -0
- package/src/core/scaffold-engine.ts +82 -0
- package/src/core/types.ts +51 -0
- package/src/core/validate-project-name.ts +59 -0
- package/src/frameworks/index.ts +111 -0
- package/src/scaffold.ts +3 -0
- package/src/templates/base.ts +209 -0
- package/src/types.ts +9 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { collectBootBindings, resolveAddons } from '../addons/registry.js'
|
|
5
|
+
import { indexTemplate, taserTsTemplate } from '../frameworks/index.js'
|
|
6
|
+
import { installPackages, resolveUserAgent } from './package-manager.js'
|
|
7
|
+
import { writeProjectConfig } from './project-config.js'
|
|
8
|
+
import { resolvePackages } from './resolve-packages.js'
|
|
9
|
+
import type { ScaffoldOptions, ScaffoldResult } from './types.js'
|
|
10
|
+
import {
|
|
11
|
+
contextTemplate,
|
|
12
|
+
gitignoreTemplate,
|
|
13
|
+
healthRouteTemplate,
|
|
14
|
+
indexRouteTemplate,
|
|
15
|
+
packageJsonTemplate,
|
|
16
|
+
rootLayoutTemplate,
|
|
17
|
+
starterManifestTemplate,
|
|
18
|
+
tsconfigTemplate,
|
|
19
|
+
tsdownConfigTemplate,
|
|
20
|
+
} from '../templates/base.js'
|
|
21
|
+
|
|
22
|
+
async function write(filePath: string, contents: string): Promise<void> {
|
|
23
|
+
await mkdir(path.dirname(filePath), { recursive: true })
|
|
24
|
+
await writeFile(filePath, contents, 'utf8')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function scaffoldProject(options: ScaffoldOptions): Promise<ScaffoldResult> {
|
|
28
|
+
const root = options.targetDir
|
|
29
|
+
const ctx = {
|
|
30
|
+
projectName: options.projectName,
|
|
31
|
+
targetDir: root,
|
|
32
|
+
framework: options.framework,
|
|
33
|
+
...(options.db ? { db: options.db, driver: options.driver } : {}),
|
|
34
|
+
...(options.logger ? { logger: options.logger } : {}),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const addons = resolveAddons(ctx)
|
|
38
|
+
const packages = resolvePackages(ctx)
|
|
39
|
+
const bootBindings = collectBootBindings(ctx)
|
|
40
|
+
|
|
41
|
+
await write(path.join(root, 'package.json'), packageJsonTemplate(options.projectName, packages.scripts))
|
|
42
|
+
await write(path.join(root, 'tsconfig.json'), tsconfigTemplate())
|
|
43
|
+
await write(path.join(root, 'tsdown.config.ts'), tsdownConfigTemplate())
|
|
44
|
+
await write(path.join(root, '.gitignore'), gitignoreTemplate())
|
|
45
|
+
await write(path.join(root, 'src/context.ts'), contextTemplate(bootBindings))
|
|
46
|
+
await write(path.join(root, 'src/taser.ts'), taserTsTemplate(options.framework))
|
|
47
|
+
await write(path.join(root, 'src/index.ts'), indexTemplate(options.framework))
|
|
48
|
+
await write(path.join(root, 'src/routes/$.ts'), rootLayoutTemplate())
|
|
49
|
+
await write(path.join(root, 'src/routes/index.get.ts'), indexRouteTemplate())
|
|
50
|
+
await write(path.join(root, 'src/routes/health.get.ts'), healthRouteTemplate(ctx))
|
|
51
|
+
await write(path.join(root, 'src/routeManifest.gen.ts'), starterManifestTemplate())
|
|
52
|
+
|
|
53
|
+
for (const addon of addons) {
|
|
54
|
+
await addon.apply(ctx, (filePath, contents) => write(path.join(root, filePath), contents))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await writeProjectConfig(root, ctx)
|
|
58
|
+
|
|
59
|
+
if (options.skipInstall) {
|
|
60
|
+
return {
|
|
61
|
+
projectName: ctx.projectName,
|
|
62
|
+
targetDir: root,
|
|
63
|
+
framework: ctx.framework,
|
|
64
|
+
...(ctx.db ? { db: ctx.db, driver: ctx.driver } : {}),
|
|
65
|
+
...(ctx.logger ? { logger: ctx.logger } : {}),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const agent = options.agent ?? resolveUserAgent()
|
|
70
|
+
await installPackages(agent, root, {
|
|
71
|
+
dependencies: packages.dependencies,
|
|
72
|
+
devDependencies: packages.devDependencies,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
projectName: ctx.projectName,
|
|
77
|
+
targetDir: root,
|
|
78
|
+
framework: ctx.framework,
|
|
79
|
+
...(ctx.db ? { db: ctx.db, driver: ctx.driver } : {}),
|
|
80
|
+
...(ctx.logger ? { logger: ctx.logger } : {}),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Agent } from 'package-manager-detector'
|
|
2
|
+
|
|
3
|
+
export type Framework = 'node' | 'express' | 'hono' | 'fastify'
|
|
4
|
+
|
|
5
|
+
export const FRAMEWORKS: readonly Framework[] = ['node', 'express', 'hono', 'fastify']
|
|
6
|
+
|
|
7
|
+
export type DbOdm = 'drizzle' | 'prisma' | 'kysely'
|
|
8
|
+
|
|
9
|
+
export const DB_ODMS: readonly DbOdm[] = ['drizzle', 'prisma', 'kysely']
|
|
10
|
+
|
|
11
|
+
export type DbDriver = 'postgres' | 'sqlite' | 'mysql'
|
|
12
|
+
|
|
13
|
+
export const DB_DRIVERS: readonly DbDriver[] = ['postgres', 'sqlite', 'mysql']
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_DB_DRIVER: DbDriver = 'sqlite'
|
|
16
|
+
|
|
17
|
+
export type LoggerId = 'pino' | 'winston'
|
|
18
|
+
|
|
19
|
+
export const LOGGERS: readonly LoggerId[] = ['pino', 'winston']
|
|
20
|
+
|
|
21
|
+
export type PackageGroups = {
|
|
22
|
+
dependencies: string[]
|
|
23
|
+
devDependencies: string[]
|
|
24
|
+
scripts: Record<string, string>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ScaffoldContext = {
|
|
28
|
+
projectName: string
|
|
29
|
+
targetDir: string
|
|
30
|
+
framework: Framework
|
|
31
|
+
db?: DbOdm
|
|
32
|
+
driver?: DbDriver
|
|
33
|
+
logger?: LoggerId
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ScaffoldOptions = ScaffoldContext & {
|
|
37
|
+
skipInstall?: boolean
|
|
38
|
+
agent?: Agent
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ScaffoldResult = ScaffoldContext
|
|
42
|
+
|
|
43
|
+
export type CapabilitiesCatalog = {
|
|
44
|
+
frameworks: Framework[]
|
|
45
|
+
db: {
|
|
46
|
+
odms: DbOdm[]
|
|
47
|
+
drivers: DbDriver[]
|
|
48
|
+
defaultDriver: DbDriver
|
|
49
|
+
}
|
|
50
|
+
loggers: LoggerId[]
|
|
51
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
const RESERVED_WINDOWS_NAMES = new Set([
|
|
4
|
+
'CON',
|
|
5
|
+
'PRN',
|
|
6
|
+
'AUX',
|
|
7
|
+
'NVR',
|
|
8
|
+
'NULL',
|
|
9
|
+
'COM1',
|
|
10
|
+
'COM2',
|
|
11
|
+
'COM3',
|
|
12
|
+
'COM4',
|
|
13
|
+
'COM5',
|
|
14
|
+
'COM6',
|
|
15
|
+
'COM7',
|
|
16
|
+
'COM8',
|
|
17
|
+
'COM9',
|
|
18
|
+
'LPT1',
|
|
19
|
+
'LPT2',
|
|
20
|
+
'LPT3',
|
|
21
|
+
'LPT4',
|
|
22
|
+
'LPT5',
|
|
23
|
+
'LPT6',
|
|
24
|
+
'LPT7',
|
|
25
|
+
'LPT8',
|
|
26
|
+
'LPT9',
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
export function validateProjectName(name: string, cwd = process.cwd()): string | undefined {
|
|
30
|
+
const trimmed = name.trim()
|
|
31
|
+
if (!trimmed) {
|
|
32
|
+
return 'Project name is required'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (trimmed.includes('..')) {
|
|
36
|
+
return 'Project name cannot contain ".."'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (trimmed.includes('/') || trimmed.includes('\\')) {
|
|
40
|
+
return 'Project name cannot contain path separators'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (trimmed.startsWith('.') || trimmed.endsWith('.')) {
|
|
44
|
+
return 'Project name cannot start or end with a dot'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const baseName = trimmed.split('.')[0]?.toUpperCase()
|
|
48
|
+
if (baseName && RESERVED_WINDOWS_NAMES.has(baseName)) {
|
|
49
|
+
return `Project name "${trimmed}" is reserved on Windows`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const targetDir = path.resolve(cwd, trimmed)
|
|
53
|
+
const relative = path.relative(cwd, targetDir)
|
|
54
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
55
|
+
return 'Project name resolves outside the current directory'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return undefined
|
|
59
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Framework } from '../core/types.js'
|
|
2
|
+
|
|
3
|
+
export function indexTemplate(framework: Framework): string {
|
|
4
|
+
switch (framework) {
|
|
5
|
+
case 'express':
|
|
6
|
+
return `import express from 'express'
|
|
7
|
+
import { createExpressHandler } from '@taserjs/adapter-express'
|
|
8
|
+
|
|
9
|
+
import { routeManifest } from '#src/routeManifest.gen.js'
|
|
10
|
+
import { t } from '#src/taser.js'
|
|
11
|
+
|
|
12
|
+
const router = t.create(routeManifest)
|
|
13
|
+
|
|
14
|
+
const taser = createExpressHandler(router)
|
|
15
|
+
const app = express()
|
|
16
|
+
taser.mount('/api{/*splat}', app)
|
|
17
|
+
|
|
18
|
+
const port = Number(process.env.PORT ?? 3000)
|
|
19
|
+
app.listen(port, () => {
|
|
20
|
+
console.log(\`Express listening on http://localhost:\${port}\`)
|
|
21
|
+
})
|
|
22
|
+
`
|
|
23
|
+
case 'hono':
|
|
24
|
+
return `import { serve } from '@hono/node-server'
|
|
25
|
+
import { Hono } from 'hono'
|
|
26
|
+
|
|
27
|
+
import { routeManifest } from '#src/routeManifest.gen.js'
|
|
28
|
+
import { t } from '#src/taser.js'
|
|
29
|
+
|
|
30
|
+
const router = t.create(routeManifest)
|
|
31
|
+
|
|
32
|
+
const app = new Hono()
|
|
33
|
+
const api = router.base('/api')
|
|
34
|
+
app.all('/api/*', c => api.native(c).fetch(c.req.raw))
|
|
35
|
+
|
|
36
|
+
const port = Number(process.env.PORT ?? 3000)
|
|
37
|
+
serve({ fetch: app.fetch, port }, () => {
|
|
38
|
+
console.log(\`Hono listening on http://localhost:\${port}\`)
|
|
39
|
+
})
|
|
40
|
+
`
|
|
41
|
+
case 'fastify':
|
|
42
|
+
return `import Fastify from 'fastify'
|
|
43
|
+
import { createFastifyHandler } from '@taserjs/adapter-fastify'
|
|
44
|
+
|
|
45
|
+
import { routeManifest } from '#src/routeManifest.gen.js'
|
|
46
|
+
import { t } from '#src/taser.js'
|
|
47
|
+
|
|
48
|
+
const router = t.create(routeManifest)
|
|
49
|
+
|
|
50
|
+
const taser = createFastifyHandler(router)
|
|
51
|
+
const app = Fastify()
|
|
52
|
+
taser.mount('/api/*', app)
|
|
53
|
+
|
|
54
|
+
const port = Number(process.env.PORT ?? 3000)
|
|
55
|
+
await app.listen({ port })
|
|
56
|
+
console.log(\`Fastify listening on http://localhost:\${port}\`)
|
|
57
|
+
`
|
|
58
|
+
case 'node':
|
|
59
|
+
default:
|
|
60
|
+
return `import { createServer } from 'node:http'
|
|
61
|
+
import { createNodeHandler } from '@taserjs/adapter-node'
|
|
62
|
+
|
|
63
|
+
import { routeManifest } from '#src/routeManifest.gen.js'
|
|
64
|
+
import { t } from '#src/taser.js'
|
|
65
|
+
|
|
66
|
+
const router = t.create(routeManifest)
|
|
67
|
+
|
|
68
|
+
const taser = createNodeHandler(router)
|
|
69
|
+
const app = createServer()
|
|
70
|
+
taser.mount('/api/*', app)
|
|
71
|
+
|
|
72
|
+
const port = Number(process.env.PORT ?? 3000)
|
|
73
|
+
app.listen(port, () => {
|
|
74
|
+
console.log(\`Node listening on http://localhost:\${port}\`)
|
|
75
|
+
})
|
|
76
|
+
`
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function taserTsTemplate(framework: Framework = 'node'): string {
|
|
81
|
+
if (framework === 'hono') {
|
|
82
|
+
return `import type { Context } from 'hono'
|
|
83
|
+
import { createTaserApp, type InferAppContext } from '@taserjs/router'
|
|
84
|
+
|
|
85
|
+
import { context } from '#src/context.js'
|
|
86
|
+
|
|
87
|
+
declare module '@taserjs/router' {
|
|
88
|
+
interface RouterRegister {
|
|
89
|
+
NativeContext: Context
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const t = createTaserApp({
|
|
94
|
+
response: { validate: true },
|
|
95
|
+
}).context(context)
|
|
96
|
+
|
|
97
|
+
export type AppContext = InferAppContext<typeof context>
|
|
98
|
+
`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return `import { createTaserApp, type InferAppContext } from '@taserjs/router'
|
|
102
|
+
|
|
103
|
+
import { context } from '#src/context.js'
|
|
104
|
+
|
|
105
|
+
export const t = createTaserApp({
|
|
106
|
+
response: { validate: true },
|
|
107
|
+
}).context(context)
|
|
108
|
+
|
|
109
|
+
export type AppContext = InferAppContext<typeof context>
|
|
110
|
+
`
|
|
111
|
+
}
|
package/src/scaffold.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { BootBinding } from '../addons/types.js'
|
|
2
|
+
import type { ScaffoldContext } from '../core/types.js'
|
|
3
|
+
|
|
4
|
+
export function packageJsonTemplate(projectName: string, scripts: Record<string, string> = {}): string {
|
|
5
|
+
const pkg = {
|
|
6
|
+
name: projectName,
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
private: true,
|
|
9
|
+
type: 'module',
|
|
10
|
+
imports: {
|
|
11
|
+
'#src/*': './src/*',
|
|
12
|
+
},
|
|
13
|
+
scripts: {
|
|
14
|
+
'dev': 'run-p dev:server dev:taser',
|
|
15
|
+
'dev:server': 'tsx watch src/index.ts',
|
|
16
|
+
'dev:taser': 'taser watch',
|
|
17
|
+
'start': 'tsx src/index.ts',
|
|
18
|
+
'generate': 'taser generate',
|
|
19
|
+
'build': 'taser generate && tsdown build',
|
|
20
|
+
'serve': 'node dist/index.js',
|
|
21
|
+
'typecheck': 'tsc --noEmit -p tsconfig.json',
|
|
22
|
+
...scripts,
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return `${JSON.stringify(pkg, null, 2)}\n`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function tsdownConfigTemplate(): string {
|
|
30
|
+
return `import { defineConfig } from 'tsdown'
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
entry: ['./src/index.ts'],
|
|
34
|
+
format: 'esm',
|
|
35
|
+
platform: 'node',
|
|
36
|
+
target: 'node20',
|
|
37
|
+
outDir: 'dist',
|
|
38
|
+
clean: true,
|
|
39
|
+
sourcemap: true,
|
|
40
|
+
})
|
|
41
|
+
`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function tsconfigTemplate(): string {
|
|
45
|
+
return `${JSON.stringify({
|
|
46
|
+
compilerOptions: {
|
|
47
|
+
target: 'ES2022',
|
|
48
|
+
lib: ['ES2022', 'DOM'],
|
|
49
|
+
module: 'ESNext',
|
|
50
|
+
moduleResolution: 'bundler',
|
|
51
|
+
paths: {
|
|
52
|
+
'#src/*': ['./src/*'],
|
|
53
|
+
},
|
|
54
|
+
strict: true,
|
|
55
|
+
skipLibCheck: true,
|
|
56
|
+
verbatimModuleSyntax: true,
|
|
57
|
+
isolatedModules: true,
|
|
58
|
+
noEmit: true,
|
|
59
|
+
types: ['node'],
|
|
60
|
+
},
|
|
61
|
+
include: ['src'],
|
|
62
|
+
}, null, 2)}\n`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function gitignoreTemplate(): string {
|
|
66
|
+
return `node_modules
|
|
67
|
+
dist
|
|
68
|
+
.DS_Store
|
|
69
|
+
*.log
|
|
70
|
+
.env
|
|
71
|
+
local.db
|
|
72
|
+
drizzle
|
|
73
|
+
`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function contextTemplate(bindings: BootBinding[]): string {
|
|
77
|
+
const imports = bindings.map(
|
|
78
|
+
binding => `import { ${binding.factoryName} } from '${binding.importPath}'`,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const bootBody = bindings.length > 0
|
|
82
|
+
? bindings.map(binding => ` ${binding.key}: ${binding.factoryName}(),`).join('\n')
|
|
83
|
+
: ''
|
|
84
|
+
|
|
85
|
+
const bootBlock = bindings.length > 0
|
|
86
|
+
? ` boot: () => ({\n${bootBody}\n }),`
|
|
87
|
+
: ''
|
|
88
|
+
|
|
89
|
+
const importBlock = imports.length > 0 ? `${imports.join('\n')}\n\n` : ''
|
|
90
|
+
|
|
91
|
+
return `${importBlock}import { createContext } from '@taserjs/router'
|
|
92
|
+
|
|
93
|
+
export const context = createContext({
|
|
94
|
+
${bootBlock}
|
|
95
|
+
request: () => ({
|
|
96
|
+
requestId: crypto.randomUUID(),
|
|
97
|
+
}),
|
|
98
|
+
})
|
|
99
|
+
`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function rootLayoutTemplate(): string {
|
|
103
|
+
return `import { bodyLimit } from '@taserjs/router/body-limit'
|
|
104
|
+
import { secureHeaders } from '@taserjs/router/secure-headers'
|
|
105
|
+
|
|
106
|
+
import { t } from '#src/taser.js'
|
|
107
|
+
|
|
108
|
+
export const Middleware = t.middleware('/$')
|
|
109
|
+
.use(secureHeaders())
|
|
110
|
+
.use(bodyLimit({ maxSize: 1_000_000 }))
|
|
111
|
+
`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function indexRouteTemplate(): string {
|
|
115
|
+
return `import { t } from '#src/taser.js'
|
|
116
|
+
|
|
117
|
+
export const Route = t.get('/').handler((ctx) => {
|
|
118
|
+
return ctx.reply.json({ message: 'Welcome to Taser' })
|
|
119
|
+
})
|
|
120
|
+
`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function healthRouteTemplate(ctx: ScaffoldContext): string {
|
|
124
|
+
const lines: string[] = []
|
|
125
|
+
|
|
126
|
+
if (ctx.logger) {
|
|
127
|
+
lines.push(' ctx.logger.info(\'health check\')')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (ctx.db) {
|
|
131
|
+
lines.push(' // ctx.db is available from context boot')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const body = lines.length > 0 ? `${lines.join('\n')}\n` : ''
|
|
135
|
+
|
|
136
|
+
return `import { t } from '#src/taser.js'
|
|
137
|
+
|
|
138
|
+
export const Route = t.get('/health').handler((ctx) => {
|
|
139
|
+
${body} return ctx.reply.json({ ok: true })
|
|
140
|
+
})
|
|
141
|
+
`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Minimal placeholder until `taser generate` runs. */
|
|
145
|
+
export function starterManifestTemplate(): string {
|
|
146
|
+
return `/* eslint-disable */
|
|
147
|
+
// Run \`pnpm generate\` (taser generate) to replace this file.
|
|
148
|
+
import { Middleware as RootSplatLayoutImport } from './routes/$.js'
|
|
149
|
+
import { Route as RootIndexGetRouteImport } from './routes/index.get.js'
|
|
150
|
+
import { Route as HealthGetRouteImport } from './routes/health.get.js'
|
|
151
|
+
|
|
152
|
+
export const routeManifest = {
|
|
153
|
+
layouts: {
|
|
154
|
+
'/$': {
|
|
155
|
+
middlewares: RootSplatLayoutImport,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
routes: {
|
|
159
|
+
'/': {
|
|
160
|
+
GET: {
|
|
161
|
+
layoutChain: ['/$'],
|
|
162
|
+
route: RootIndexGetRouteImport,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
'/health': {
|
|
166
|
+
GET: {
|
|
167
|
+
layoutChain: ['/$'],
|
|
168
|
+
route: HealthGetRouteImport,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
} as const
|
|
173
|
+
|
|
174
|
+
export type RoutePathGen = '/' | '/health'
|
|
175
|
+
export type LayoutIdGen = '/$'
|
|
176
|
+
export type LayoutTreeGen = {
|
|
177
|
+
'/$': {
|
|
178
|
+
parent: null
|
|
179
|
+
middlewares: typeof RootSplatLayoutImport
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export type RouteByPathMethodGen = {
|
|
183
|
+
'/': {
|
|
184
|
+
GET: {
|
|
185
|
+
parent: '/$'
|
|
186
|
+
layoutChain: ['/$']
|
|
187
|
+
route: typeof RootIndexGetRouteImport
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
'/health': {
|
|
191
|
+
GET: {
|
|
192
|
+
parent: '/$'
|
|
193
|
+
layoutChain: ['/$']
|
|
194
|
+
route: typeof HealthGetRouteImport
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export type RouteManifest = typeof routeManifest
|
|
199
|
+
|
|
200
|
+
declare module '@taserjs/router' {
|
|
201
|
+
interface RouterRegister {
|
|
202
|
+
RoutePath: RoutePathGen
|
|
203
|
+
LayoutId: LayoutIdGen
|
|
204
|
+
LayoutTree: LayoutTreeGen
|
|
205
|
+
RouteByPathMethod: RouteByPathMethodGen
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
`
|
|
209
|
+
}
|