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,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
|
+
}
|