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.
Files changed (139) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +22 -0
  3. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/cjs/addons/drizzle/index.cjs +124 -0
  5. package/dist/cjs/addons/drizzle/index.cjs.map +1 -0
  6. package/dist/cjs/addons/drizzle/index.d.cts +2 -0
  7. package/dist/cjs/addons/kysely/index.cjs +96 -0
  8. package/dist/cjs/addons/kysely/index.cjs.map +1 -0
  9. package/dist/cjs/addons/kysely/index.d.cts +2 -0
  10. package/dist/cjs/addons/pino/index.cjs +30 -0
  11. package/dist/cjs/addons/pino/index.cjs.map +1 -0
  12. package/dist/cjs/addons/pino/index.d.cts +2 -0
  13. package/dist/cjs/addons/prisma/index.cjs +66 -0
  14. package/dist/cjs/addons/prisma/index.cjs.map +1 -0
  15. package/dist/cjs/addons/prisma/index.d.cts +2 -0
  16. package/dist/cjs/addons/registry.cjs +52 -0
  17. package/dist/cjs/addons/registry.cjs.map +1 -0
  18. package/dist/cjs/addons/registry.d.cts +5 -0
  19. package/dist/cjs/addons/types.d.cts +16 -0
  20. package/dist/cjs/addons/winston/index.cjs +34 -0
  21. package/dist/cjs/addons/winston/index.cjs.map +1 -0
  22. package/dist/cjs/addons/winston/index.d.cts +2 -0
  23. package/dist/cjs/cli.cjs +56 -0
  24. package/dist/cjs/cli.cjs.map +1 -0
  25. package/dist/cjs/cli.d.cts +2 -0
  26. package/dist/cjs/commands/create.cjs +243 -0
  27. package/dist/cjs/commands/create.cjs.map +1 -0
  28. package/dist/cjs/commands/create.d.cts +11 -0
  29. package/dist/cjs/core/json-output.cjs +16 -0
  30. package/dist/cjs/core/json-output.cjs.map +1 -0
  31. package/dist/cjs/core/json-output.d.cts +4 -0
  32. package/dist/cjs/core/package-manager.cjs +64 -0
  33. package/dist/cjs/core/package-manager.cjs.map +1 -0
  34. package/dist/cjs/core/package-manager.d.cts +7 -0
  35. package/dist/cjs/core/parse-options.cjs +59 -0
  36. package/dist/cjs/core/parse-options.cjs.map +1 -0
  37. package/dist/cjs/core/parse-options.d.cts +18 -0
  38. package/dist/cjs/core/project-config.cjs +18 -0
  39. package/dist/cjs/core/project-config.cjs.map +1 -0
  40. package/dist/cjs/core/project-config.d.cts +2 -0
  41. package/dist/cjs/core/resolve-packages.cjs +52 -0
  42. package/dist/cjs/core/resolve-packages.cjs.map +1 -0
  43. package/dist/cjs/core/resolve-packages.d.cts +5 -0
  44. package/dist/cjs/core/scaffold-engine.cjs +73 -0
  45. package/dist/cjs/core/scaffold-engine.cjs.map +1 -0
  46. package/dist/cjs/core/scaffold-engine.d.cts +2 -0
  47. package/dist/cjs/core/types.cjs +27 -0
  48. package/dist/cjs/core/types.cjs.map +1 -0
  49. package/dist/cjs/core/types.d.cts +37 -0
  50. package/dist/cjs/core/validate-project-name.cjs +45 -0
  51. package/dist/cjs/core/validate-project-name.cjs.map +1 -0
  52. package/dist/cjs/core/validate-project-name.d.cts +1 -0
  53. package/dist/cjs/frameworks/index.cjs +106 -0
  54. package/dist/cjs/frameworks/index.cjs.map +1 -0
  55. package/dist/cjs/frameworks/index.d.cts +3 -0
  56. package/dist/cjs/scaffold.d.cts +3 -0
  57. package/dist/cjs/templates/base.cjs +186 -0
  58. package/dist/cjs/templates/base.cjs.map +1 -0
  59. package/dist/cjs/templates/base.d.cts +12 -0
  60. package/dist/cjs/types.d.cts +1 -0
  61. package/dist/esm/addons/drizzle/index.d.ts +2 -0
  62. package/dist/esm/addons/drizzle/index.js +124 -0
  63. package/dist/esm/addons/drizzle/index.js.map +1 -0
  64. package/dist/esm/addons/kysely/index.d.ts +2 -0
  65. package/dist/esm/addons/kysely/index.js +96 -0
  66. package/dist/esm/addons/kysely/index.js.map +1 -0
  67. package/dist/esm/addons/pino/index.d.ts +2 -0
  68. package/dist/esm/addons/pino/index.js +30 -0
  69. package/dist/esm/addons/pino/index.js.map +1 -0
  70. package/dist/esm/addons/prisma/index.d.ts +2 -0
  71. package/dist/esm/addons/prisma/index.js +66 -0
  72. package/dist/esm/addons/prisma/index.js.map +1 -0
  73. package/dist/esm/addons/registry.d.ts +5 -0
  74. package/dist/esm/addons/registry.js +50 -0
  75. package/dist/esm/addons/registry.js.map +1 -0
  76. package/dist/esm/addons/types.d.ts +16 -0
  77. package/dist/esm/addons/winston/index.d.ts +2 -0
  78. package/dist/esm/addons/winston/index.js +34 -0
  79. package/dist/esm/addons/winston/index.js.map +1 -0
  80. package/dist/esm/cli.d.ts +2 -0
  81. package/dist/esm/cli.js +53 -0
  82. package/dist/esm/cli.js.map +1 -0
  83. package/dist/esm/commands/create.d.ts +11 -0
  84. package/dist/esm/commands/create.js +238 -0
  85. package/dist/esm/commands/create.js.map +1 -0
  86. package/dist/esm/core/json-output.d.ts +4 -0
  87. package/dist/esm/core/json-output.js +14 -0
  88. package/dist/esm/core/json-output.js.map +1 -0
  89. package/dist/esm/core/package-manager.d.ts +7 -0
  90. package/dist/esm/core/package-manager.js +62 -0
  91. package/dist/esm/core/package-manager.js.map +1 -0
  92. package/dist/esm/core/parse-options.d.ts +18 -0
  93. package/dist/esm/core/parse-options.js +56 -0
  94. package/dist/esm/core/parse-options.js.map +1 -0
  95. package/dist/esm/core/project-config.d.ts +2 -0
  96. package/dist/esm/core/project-config.js +16 -0
  97. package/dist/esm/core/project-config.js.map +1 -0
  98. package/dist/esm/core/resolve-packages.d.ts +5 -0
  99. package/dist/esm/core/resolve-packages.js +52 -0
  100. package/dist/esm/core/resolve-packages.js.map +1 -0
  101. package/dist/esm/core/scaffold-engine.d.ts +2 -0
  102. package/dist/esm/core/scaffold-engine.js +71 -0
  103. package/dist/esm/core/scaffold-engine.js.map +1 -0
  104. package/dist/esm/core/types.d.ts +37 -0
  105. package/dist/esm/core/types.js +23 -0
  106. package/dist/esm/core/types.js.map +1 -0
  107. package/dist/esm/core/validate-project-name.d.ts +1 -0
  108. package/dist/esm/core/validate-project-name.js +43 -0
  109. package/dist/esm/core/validate-project-name.js.map +1 -0
  110. package/dist/esm/frameworks/index.d.ts +3 -0
  111. package/dist/esm/frameworks/index.js +105 -0
  112. package/dist/esm/frameworks/index.js.map +1 -0
  113. package/dist/esm/scaffold.d.ts +3 -0
  114. package/dist/esm/templates/base.d.ts +12 -0
  115. package/dist/esm/templates/base.js +178 -0
  116. package/dist/esm/templates/base.js.map +1 -0
  117. package/dist/esm/types.d.ts +1 -0
  118. package/package.json +45 -0
  119. package/src/addons/drizzle/index.ts +145 -0
  120. package/src/addons/kysely/index.ts +111 -0
  121. package/src/addons/pino/index.ts +30 -0
  122. package/src/addons/prisma/index.ts +81 -0
  123. package/src/addons/registry.ts +73 -0
  124. package/src/addons/types.ts +19 -0
  125. package/src/addons/winston/index.ts +34 -0
  126. package/src/cli.ts +57 -0
  127. package/src/commands/create.ts +253 -0
  128. package/src/core/json-output.ts +13 -0
  129. package/src/core/package-manager.ts +68 -0
  130. package/src/core/parse-options.ts +97 -0
  131. package/src/core/project-config.ts +27 -0
  132. package/src/core/resolve-packages.ts +62 -0
  133. package/src/core/scaffold-engine.ts +82 -0
  134. package/src/core/types.ts +51 -0
  135. package/src/core/validate-project-name.ts +59 -0
  136. package/src/frameworks/index.ts +111 -0
  137. package/src/scaffold.ts +3 -0
  138. package/src/templates/base.ts +209 -0
  139. package/src/types.ts +9 -0
@@ -0,0 +1,73 @@
1
+ import { drizzleAddon } from './drizzle/index.js'
2
+ import { kyselyAddon } from './kysely/index.js'
3
+ import { pinoAddon } from './pino/index.js'
4
+ import { prismaAddon } from './prisma/index.js'
5
+ import type { AddonDefinition } from './types.js'
6
+ import { winstonAddon } from './winston/index.js'
7
+ import type { CapabilitiesCatalog, ScaffoldContext } from '../core/types.js'
8
+ import {
9
+ DB_DRIVERS,
10
+ DB_ODMS,
11
+ DEFAULT_DB_DRIVER,
12
+ FRAMEWORKS,
13
+ LOGGERS,
14
+ } from '../core/types.js'
15
+
16
+ const ALL_ADDONS: AddonDefinition[] = [
17
+ drizzleAddon,
18
+ prismaAddon,
19
+ kyselyAddon,
20
+ pinoAddon,
21
+ winstonAddon,
22
+ ]
23
+
24
+ const DB_ADDONS = ALL_ADDONS.filter(addon => addon.category === 'database')
25
+ const LOGGER_ADDONS = ALL_ADDONS.filter(addon => addon.category === 'logger')
26
+
27
+ export function getCapabilitiesCatalog(): CapabilitiesCatalog {
28
+ return {
29
+ frameworks: [...FRAMEWORKS],
30
+ db: {
31
+ odms: [...DB_ODMS],
32
+ drivers: [...DB_DRIVERS],
33
+ defaultDriver: DEFAULT_DB_DRIVER,
34
+ },
35
+ loggers: [...LOGGERS],
36
+ }
37
+ }
38
+
39
+ export function resolveAddons(ctx: ScaffoldContext): AddonDefinition[] {
40
+ const selected: AddonDefinition[] = []
41
+
42
+ if (ctx.db) {
43
+ const dbAddon = DB_ADDONS.find(addon => addon.id === ctx.db)
44
+ if (!dbAddon) {
45
+ throw new Error(`Unknown database addon "${ctx.db}"`)
46
+ }
47
+ selected.push(dbAddon)
48
+ }
49
+
50
+ if (ctx.logger) {
51
+ const loggerAddon = LOGGER_ADDONS.find(addon => addon.id === ctx.logger)
52
+ if (!loggerAddon) {
53
+ throw new Error(`Unknown logger addon "${ctx.logger}"`)
54
+ }
55
+ selected.push(loggerAddon)
56
+ }
57
+
58
+ const dbCount = selected.filter(addon => addon.category === 'database').length
59
+ if (dbCount > 1) {
60
+ throw new Error('Only one database addon can be selected')
61
+ }
62
+
63
+ const loggerCount = selected.filter(addon => addon.category === 'logger').length
64
+ if (loggerCount > 1) {
65
+ throw new Error('Only one logger addon can be selected')
66
+ }
67
+
68
+ return selected
69
+ }
70
+
71
+ export function collectBootBindings(ctx: ScaffoldContext) {
72
+ return resolveAddons(ctx).map(addon => addon.bootBinding(ctx))
73
+ }
@@ -0,0 +1,19 @@
1
+ import type { ScaffoldContext } from '../core/types.js'
2
+
3
+ export type BootBinding = {
4
+ key: string
5
+ importPath: string
6
+ factoryName: string
7
+ }
8
+
9
+ export type FileWriter = (filePath: string, contents: string) => Promise<void>
10
+
11
+ export type AddonDefinition = {
12
+ id: string
13
+ category: 'database' | 'logger'
14
+ dependencies: (ctx: ScaffoldContext) => string[]
15
+ devDependencies: (ctx: ScaffoldContext) => string[]
16
+ scripts?: (ctx: ScaffoldContext) => Record<string, string>
17
+ bootBinding: (ctx: ScaffoldContext) => BootBinding
18
+ apply: (ctx: ScaffoldContext, write: FileWriter) => Promise<void>
19
+ }
@@ -0,0 +1,34 @@
1
+ import type { AddonDefinition } from '../types.js'
2
+
3
+ export const winstonAddon: AddonDefinition = {
4
+ id: 'winston',
5
+ category: 'logger',
6
+ dependencies() {
7
+ return ['winston']
8
+ },
9
+ devDependencies() {
10
+ return []
11
+ },
12
+ bootBinding() {
13
+ return {
14
+ key: 'logger',
15
+ importPath: './logger.js',
16
+ factoryName: 'createLogger',
17
+ }
18
+ },
19
+ async apply(_ctx, write) {
20
+ await write(
21
+ 'src/logger.ts',
22
+ `import winston from 'winston'
23
+
24
+ export function createLogger() {
25
+ return winston.createLogger({
26
+ level: process.env.LOG_LEVEL ?? 'info',
27
+ format: winston.format.json(),
28
+ transports: [new winston.transports.Console()],
29
+ })
30
+ }
31
+ `,
32
+ )
33
+ },
34
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util'
3
+
4
+ import * as p from '@clack/prompts'
5
+ import color from 'picocolors'
6
+
7
+ import { buildParsedArgsFromCli, runCreateCommand } from './commands/create.js'
8
+ import { printJsonError } from './core/json-output.js'
9
+
10
+ async function main(): Promise<void> {
11
+ const { values, positionals } = parseArgs({
12
+ args: process.argv.slice(2),
13
+ options: {
14
+ framework: { type: 'string' },
15
+ db: { type: 'string' },
16
+ logger: { type: 'string' },
17
+ y: { type: 'boolean', short: 'y' },
18
+ noInstall: { type: 'boolean' },
19
+ json: { type: 'boolean' },
20
+ },
21
+ allowPositionals: true,
22
+ })
23
+
24
+ const parsed = buildParsedArgsFromCli(
25
+ {
26
+ ...(values.framework !== undefined ? { framework: values.framework } : {}),
27
+ ...(values.db !== undefined ? { db: values.db } : {}),
28
+ ...(values.logger !== undefined ? { logger: values.logger } : {}),
29
+ ...(values.y !== undefined ? { y: values.y } : {}),
30
+ ...(values.noInstall !== undefined ? { noInstall: values.noInstall } : {}),
31
+ ...(values.json !== undefined ? { json: values.json } : {}),
32
+ },
33
+ positionals,
34
+ )
35
+
36
+ if (!parsed.json) {
37
+ console.clear()
38
+ p.intro(color.bgCyan(color.black(' create-taser ')))
39
+ }
40
+
41
+ try {
42
+ await runCreateCommand(parsed)
43
+ }
44
+ catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error)
46
+ if (parsed.json) {
47
+ printJsonError(message)
48
+ process.exit(1)
49
+ }
50
+ throw error
51
+ }
52
+ }
53
+
54
+ main().catch((error: unknown) => {
55
+ console.error(error)
56
+ process.exit(1)
57
+ })
@@ -0,0 +1,253 @@
1
+ import path from 'node:path'
2
+
3
+ import * as p from '@clack/prompts'
4
+ import color from 'picocolors'
5
+
6
+ import { getCapabilitiesCatalog } from '../addons/registry.js'
7
+ import { printCapabilitiesCatalog, printJsonError, printScaffoldResult } from '../core/json-output.js'
8
+ import {
9
+ parseDbFlag,
10
+ parseFrameworkFlag,
11
+ parseLoggerFlag,
12
+ type ParsedCreateArgs,
13
+ resolveScaffoldDefaults,
14
+ } from '../core/parse-options.js'
15
+ import { scaffoldProject } from '../core/scaffold-engine.js'
16
+ import type { DbDriver, DbOdm, Framework, LoggerId, ScaffoldResult } from '../core/types.js'
17
+ import { DB_DRIVERS, DB_ODMS, FRAMEWORKS, LOGGERS } from '../core/types.js'
18
+ import { validateProjectName } from '../core/validate-project-name.js'
19
+ import { resolveUserAgent, runScript } from '../core/package-manager.js'
20
+
21
+ function isFramework(value: unknown): value is Framework {
22
+ return typeof value === 'string' && (FRAMEWORKS as readonly string[]).includes(value)
23
+ }
24
+
25
+ function isDbOdm(value: unknown): value is DbOdm {
26
+ return typeof value === 'string' && (DB_ODMS as readonly string[]).includes(value)
27
+ }
28
+
29
+ function isDbDriver(value: unknown): value is DbDriver {
30
+ return typeof value === 'string' && (DB_DRIVERS as readonly string[]).includes(value)
31
+ }
32
+
33
+ function isLoggerId(value: unknown): value is LoggerId {
34
+ return typeof value === 'string' && (LOGGERS as readonly string[]).includes(value)
35
+ }
36
+
37
+ async function promptInteractiveOptions(
38
+ args: ParsedCreateArgs,
39
+ ): Promise<ParsedCreateArgs> {
40
+ const projectName = args.projectName
41
+ ?? await p.text({
42
+ message: 'Project name',
43
+ placeholder: 'my-taser-app',
44
+ defaultValue: 'my-taser-app',
45
+ validate(value) {
46
+ return validateProjectName(value)
47
+ },
48
+ })
49
+
50
+ if (p.isCancel(projectName)) {
51
+ p.cancel('Scaffold cancelled.')
52
+ process.exit(0)
53
+ }
54
+
55
+ const framework = args.framework
56
+ ?? await p.select({
57
+ message: 'Framework adapter',
58
+ options: [
59
+ { value: 'node', label: 'Plain Node.js', hint: 'default' },
60
+ { value: 'express', label: 'Express' },
61
+ { value: 'hono', label: 'Hono' },
62
+ { value: 'fastify', label: 'Fastify' },
63
+ ],
64
+ initialValue: 'node',
65
+ })
66
+
67
+ if (p.isCancel(framework) || !isFramework(framework)) {
68
+ p.cancel('Scaffold cancelled.')
69
+ process.exit(0)
70
+ }
71
+
72
+ const dbChoice = args.db
73
+ ?? await p.select({
74
+ message: 'Database',
75
+ options: [
76
+ { value: 'none', label: 'None', hint: 'default' },
77
+ { value: 'drizzle', label: 'Drizzle' },
78
+ { value: 'prisma', label: 'Prisma' },
79
+ { value: 'kysely', label: 'Kysely' },
80
+ ],
81
+ initialValue: 'none',
82
+ })
83
+
84
+ if (p.isCancel(dbChoice)) {
85
+ p.cancel('Scaffold cancelled.')
86
+ process.exit(0)
87
+ }
88
+
89
+ let db: DbOdm | undefined
90
+ let driver: DbDriver | undefined
91
+
92
+ if (dbChoice !== 'none' && isDbOdm(dbChoice)) {
93
+ db = dbChoice
94
+ const driverChoice = args.driver
95
+ ?? await p.select({
96
+ message: 'Database driver',
97
+ options: [
98
+ { value: 'sqlite', label: 'SQLite', hint: 'default' },
99
+ { value: 'postgres', label: 'PostgreSQL' },
100
+ { value: 'mysql', label: 'MySQL' },
101
+ ],
102
+ initialValue: 'sqlite',
103
+ })
104
+
105
+ if (p.isCancel(driverChoice) || !isDbDriver(driverChoice)) {
106
+ p.cancel('Scaffold cancelled.')
107
+ process.exit(0)
108
+ }
109
+ driver = driverChoice
110
+ }
111
+
112
+ const loggerChoice = args.logger
113
+ ?? await p.select({
114
+ message: 'Logger',
115
+ options: [
116
+ { value: 'none', label: 'None', hint: 'default' },
117
+ { value: 'pino', label: 'Pino' },
118
+ { value: 'winston', label: 'Winston' },
119
+ ],
120
+ initialValue: 'none',
121
+ })
122
+
123
+ if (p.isCancel(loggerChoice)) {
124
+ p.cancel('Scaffold cancelled.')
125
+ process.exit(0)
126
+ }
127
+
128
+ const logger = loggerChoice === 'none' ? undefined : isLoggerId(loggerChoice) ? loggerChoice : undefined
129
+
130
+ return {
131
+ projectName: String(projectName).trim(),
132
+ framework,
133
+ yes: args.yes,
134
+ noInstall: args.noInstall,
135
+ json: args.json,
136
+ ...(db ? { db, driver: driver! } : {}),
137
+ ...(logger ? { logger } : {}),
138
+ }
139
+ }
140
+
141
+ export async function runCreateCommand(args: ParsedCreateArgs): Promise<ScaffoldResult | undefined> {
142
+ if (args.json && !args.projectName) {
143
+ printCapabilitiesCatalog(getCapabilitiesCatalog())
144
+ return undefined
145
+ }
146
+
147
+ const cwd = process.cwd()
148
+ const interactive = !args.yes
149
+ const resolved = interactive
150
+ ? await promptInteractiveOptions(args)
151
+ : {
152
+ ...args,
153
+ projectName: args.projectName?.trim(),
154
+ framework: args.framework ?? 'node',
155
+ }
156
+
157
+ if (!resolved.projectName) {
158
+ const message = 'Project name is required'
159
+ if (args.json) {
160
+ printJsonError(message)
161
+ process.exit(1)
162
+ }
163
+ throw new Error(message)
164
+ }
165
+
166
+ const nameError = validateProjectName(resolved.projectName, cwd)
167
+ if (nameError) {
168
+ if (args.json) {
169
+ printJsonError(nameError)
170
+ process.exit(1)
171
+ }
172
+ throw new Error(nameError)
173
+ }
174
+
175
+ const targetDir = path.resolve(cwd, resolved.projectName)
176
+ const scaffoldCtx = resolveScaffoldDefaults({
177
+ ...resolved,
178
+ projectName: resolved.projectName,
179
+ })
180
+ scaffoldCtx.targetDir = targetDir
181
+
182
+ if (!args.json) {
183
+ const spinner = p.spinner()
184
+ spinner.start('Scaffolding project')
185
+ try {
186
+ const result = await scaffoldProject({
187
+ ...scaffoldCtx,
188
+ targetDir,
189
+ skipInstall: args.noInstall,
190
+ agent: resolveUserAgent(),
191
+ })
192
+ spinner.stop('Project created')
193
+ const cdPath = path.relative(cwd, targetDir) || '.'
194
+ p.note(
195
+ [
196
+ `cd ${cdPath}`,
197
+ runScript(resolveUserAgent(), 'dev'),
198
+ ].join('\n'),
199
+ 'Next steps',
200
+ )
201
+ p.outro(color.green('Done.'))
202
+ return result
203
+ }
204
+ catch (error) {
205
+ spinner.stop('Scaffold failed')
206
+ throw error
207
+ }
208
+ }
209
+
210
+ const result = await scaffoldProject({
211
+ ...scaffoldCtx,
212
+ targetDir,
213
+ skipInstall: args.noInstall,
214
+ agent: resolveUserAgent(),
215
+ })
216
+ printScaffoldResult(result)
217
+ return result
218
+ }
219
+
220
+ export function buildParsedArgsFromCli(values: {
221
+ framework?: string
222
+ db?: string
223
+ logger?: string
224
+ y?: boolean
225
+ noInstall?: boolean
226
+ json?: boolean
227
+ }, positionals: string[]): ParsedCreateArgs {
228
+ const args: ParsedCreateArgs = {
229
+ yes: values.y ?? false,
230
+ noInstall: values.noInstall ?? false,
231
+ json: values.json ?? false,
232
+ }
233
+
234
+ if (positionals[0]) {
235
+ args.projectName = positionals[0]
236
+ }
237
+
238
+ if (values.framework) {
239
+ args.framework = parseFrameworkFlag(values.framework)
240
+ }
241
+
242
+ if (values.db) {
243
+ const parsed = parseDbFlag(values.db)
244
+ args.db = parsed.db
245
+ args.driver = parsed.driver
246
+ }
247
+
248
+ if (values.logger) {
249
+ args.logger = parseLoggerFlag(values.logger)
250
+ }
251
+
252
+ return args
253
+ }
@@ -0,0 +1,13 @@
1
+ import type { CapabilitiesCatalog, ScaffoldResult } from './types.js'
2
+
3
+ export function printCapabilitiesCatalog(catalog: CapabilitiesCatalog): void {
4
+ console.log(JSON.stringify(catalog, null, 2))
5
+ }
6
+
7
+ export function printScaffoldResult(result: ScaffoldResult): void {
8
+ console.log(JSON.stringify(result, null, 2))
9
+ }
10
+
11
+ export function printJsonError(message: string): void {
12
+ console.error(JSON.stringify({ error: message }))
13
+ }
@@ -0,0 +1,68 @@
1
+ import { spawn } from 'node:child_process'
2
+
3
+ import type { Agent, ResolvedCommand } from 'package-manager-detector'
4
+ import { resolveCommand } from 'package-manager-detector/commands'
5
+ import { getUserAgent } from 'package-manager-detector/detect'
6
+
7
+ export function resolveUserAgent(): Agent {
8
+ return getUserAgent() || 'npm'
9
+ }
10
+
11
+ function runCommand(command: string, args: string[], cwd: string): Promise<void> {
12
+ return new Promise((resolve, reject) => {
13
+ const child = spawn(command, args, {
14
+ cwd,
15
+ stdio: 'inherit',
16
+ shell: false,
17
+ env: process.env,
18
+ })
19
+ child.on('error', reject)
20
+ child.on('close', (code) => {
21
+ if (code === 0) {
22
+ resolve()
23
+ return
24
+ }
25
+ reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`))
26
+ })
27
+ })
28
+ }
29
+
30
+ const fallback = {
31
+ agent: 'npm',
32
+ install: (packages: string[], dev: boolean) => {
33
+ return ['install', ...packages, dev ? '-D' : '']
34
+ },
35
+ }
36
+
37
+ function resolveInstallCommand(agent: Agent, packages: string[], dev: boolean): ResolvedCommand {
38
+ const command = resolveCommand(agent, 'install', packages)
39
+ if (command) return command
40
+
41
+ return { command: fallback.agent, args: fallback.install(packages, dev) }
42
+ }
43
+
44
+ export async function installPackages(
45
+ agent: Agent,
46
+ cwd: string,
47
+ { dependencies, devDependencies }: {
48
+ dependencies: string[]
49
+ devDependencies: string[]
50
+ },
51
+ ): Promise<void> {
52
+ if (dependencies.length > 0) {
53
+ const { command, args } = resolveInstallCommand(agent, dependencies, false)
54
+ await runCommand(command, args, cwd)
55
+ }
56
+ if (devDependencies.length > 0) {
57
+ const { command, args } = resolveInstallCommand(agent, devDependencies, true)
58
+ await runCommand(command, args, cwd)
59
+ }
60
+ }
61
+
62
+ export function runScript(agent: Agent, script: string): string {
63
+ const resolved = resolveCommand(agent, 'run', [script])
64
+ if (!resolved) {
65
+ throw new Error(`Unable to resolve run command for ${agent}`)
66
+ }
67
+ return `${resolved.command} ${resolved.args.join(' ')}`
68
+ }
@@ -0,0 +1,97 @@
1
+ import {
2
+ DB_DRIVERS,
3
+ DB_ODMS,
4
+ DEFAULT_DB_DRIVER,
5
+ FRAMEWORKS,
6
+ LOGGERS,
7
+ type DbDriver,
8
+ type DbOdm,
9
+ type Framework,
10
+ type LoggerId,
11
+ type ScaffoldContext,
12
+ } from './types.js'
13
+
14
+ export type ParsedCreateArgs = {
15
+ projectName?: string
16
+ framework?: Framework
17
+ db?: DbOdm
18
+ driver?: DbDriver
19
+ logger?: LoggerId
20
+ yes: boolean
21
+ noInstall: boolean
22
+ json: boolean
23
+ }
24
+
25
+ function isFramework(value: string): value is Framework {
26
+ return (FRAMEWORKS as readonly string[]).includes(value)
27
+ }
28
+
29
+ function isDbOdm(value: string): value is DbOdm {
30
+ return (DB_ODMS as readonly string[]).includes(value)
31
+ }
32
+
33
+ function isDbDriver(value: string): value is DbDriver {
34
+ return (DB_DRIVERS as readonly string[]).includes(value)
35
+ }
36
+
37
+ function isLoggerId(value: string): value is LoggerId {
38
+ return (LOGGERS as readonly string[]).includes(value)
39
+ }
40
+
41
+ export function parseDbFlag(value: string): { db: DbOdm, driver: DbDriver } {
42
+ const [odm, driver] = value.split(':')
43
+ if (!odm || !isDbOdm(odm)) {
44
+ throw new Error(`Invalid --db value "${value}". Use drizzle, prisma, or kysely (optionally with :postgres, :sqlite, or :mysql).`)
45
+ }
46
+
47
+ if (driver === undefined || driver === '') {
48
+ return { db: odm, driver: DEFAULT_DB_DRIVER }
49
+ }
50
+
51
+ if (!isDbDriver(driver)) {
52
+ throw new Error(`Invalid database driver "${driver}". Use postgres, sqlite, or mysql.`)
53
+ }
54
+
55
+ return { db: odm, driver }
56
+ }
57
+
58
+ export function resolveScaffoldDefaults(args: ParsedCreateArgs): ScaffoldContext {
59
+ const framework = args.framework ?? 'node'
60
+ const db = args.db
61
+ const logger = args.logger
62
+
63
+ if (!args.projectName) {
64
+ throw new Error('Project name is required')
65
+ }
66
+
67
+ const result: ScaffoldContext = {
68
+ projectName: args.projectName.trim(),
69
+ targetDir: '',
70
+ framework,
71
+ }
72
+
73
+ if (db) {
74
+ result.db = db
75
+ result.driver = args.driver ?? DEFAULT_DB_DRIVER
76
+ }
77
+
78
+ if (logger) {
79
+ result.logger = logger
80
+ }
81
+
82
+ return result
83
+ }
84
+
85
+ export function parseFrameworkFlag(value: string): Framework {
86
+ if (!isFramework(value)) {
87
+ throw new Error(`Invalid --framework "${value}". Use node, express, hono, or fastify.`)
88
+ }
89
+ return value
90
+ }
91
+
92
+ export function parseLoggerFlag(value: string): LoggerId {
93
+ if (!isLoggerId(value)) {
94
+ throw new Error(`Invalid --logger "${value}". Use pino or winston.`)
95
+ }
96
+ return value
97
+ }
@@ -0,0 +1,27 @@
1
+ import { writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ import type { ScaffoldContext } from './types.js'
5
+
6
+ export async function writeProjectConfig(root: string, ctx: ScaffoldContext): Promise<void> {
7
+ const config: Record<string, string> = {
8
+ framework: ctx.framework,
9
+ }
10
+
11
+ if (ctx.db) {
12
+ config.db = ctx.db
13
+ if (ctx.driver) {
14
+ config.driver = ctx.driver
15
+ }
16
+ }
17
+
18
+ if (ctx.logger) {
19
+ config.logger = ctx.logger
20
+ }
21
+
22
+ await writeFile(
23
+ path.join(root, '.taser.json'),
24
+ `${JSON.stringify(config, null, 2)}\n`,
25
+ 'utf8',
26
+ )
27
+ }
@@ -0,0 +1,62 @@
1
+ import { resolveAddons } from '../addons/registry.js'
2
+ import type { PackageGroups, ScaffoldContext, Framework } from '../core/types.js'
3
+
4
+ function frameworkPackages(framework: Framework): PackageGroups {
5
+ const dependencies = ['@taserjs/router']
6
+ const devDependencies = [
7
+ '@taserjs/router-cli',
8
+ 'npm-run-all2',
9
+ 'tsdown',
10
+ 'tsx',
11
+ 'typescript',
12
+ ]
13
+ const scripts: Record<string, string> = {}
14
+
15
+ switch (framework) {
16
+ case 'express':
17
+ dependencies.push('@taserjs/adapter-express', 'express')
18
+ devDependencies.push('@types/express')
19
+ break
20
+ case 'hono':
21
+ dependencies.push('@hono/node-server', 'hono')
22
+ break
23
+ case 'fastify':
24
+ dependencies.push('@taserjs/adapter-fastify', 'fastify')
25
+ break
26
+ case 'node':
27
+ default:
28
+ dependencies.push('@taserjs/adapter-node')
29
+ break
30
+ }
31
+
32
+ return { dependencies, devDependencies, scripts }
33
+ }
34
+
35
+ export function resolvePackages(ctx: ScaffoldContext): PackageGroups {
36
+ const base = frameworkPackages(ctx.framework)
37
+ const addons = resolveAddons(ctx)
38
+
39
+ const dependencies = [...base.dependencies]
40
+ const devDependencies = [...base.devDependencies]
41
+ const scripts = { ...base.scripts }
42
+
43
+ for (const addon of addons) {
44
+ dependencies.push(...addon.dependencies(ctx))
45
+ devDependencies.push(...addon.devDependencies(ctx))
46
+ if (addon.scripts) {
47
+ Object.assign(scripts, addon.scripts(ctx))
48
+ }
49
+ }
50
+
51
+ return {
52
+ dependencies: [...new Set(dependencies)],
53
+ devDependencies: [...new Set(devDependencies)],
54
+ scripts,
55
+ }
56
+ }
57
+
58
+ // Backward-compatible export for tests during migration
59
+ export function getPackageGroups(framework: Framework): Omit<PackageGroups, 'scripts'> & { scripts?: Record<string, string> } {
60
+ const groups = frameworkPackages(framework)
61
+ return groups
62
+ }