create-taserjs 0.0.6 → 0.1.1

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 (92) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/addons/prisma/index.cjs +12 -5
  3. package/dist/cjs/addons/prisma/index.cjs.map +1 -1
  4. package/dist/cjs/addons/registry.cjs +3 -1
  5. package/dist/cjs/addons/registry.cjs.map +1 -1
  6. package/dist/cjs/addons/validators.cjs +10 -13
  7. package/dist/cjs/addons/validators.cjs.map +1 -1
  8. package/dist/cjs/cli.cjs +7 -5
  9. package/dist/cjs/cli.cjs.map +1 -1
  10. package/dist/cjs/commands/create.cjs +72 -49
  11. package/dist/cjs/commands/create.cjs.map +1 -1
  12. package/dist/cjs/commands/create.d.cts +2 -1
  13. package/dist/cjs/core/parse-options.cjs +48 -22
  14. package/dist/cjs/core/parse-options.cjs.map +1 -1
  15. package/dist/cjs/core/parse-options.d.cts +19 -4
  16. package/dist/cjs/core/project-config.cjs +5 -1
  17. package/dist/cjs/core/project-config.cjs.map +1 -1
  18. package/dist/cjs/core/resolve-packages.cjs +35 -65
  19. package/dist/cjs/core/resolve-packages.cjs.map +1 -1
  20. package/dist/cjs/core/resolve-packages.d.cts +1 -4
  21. package/dist/cjs/core/scaffold-engine.cjs +16 -27
  22. package/dist/cjs/core/scaffold-engine.cjs.map +1 -1
  23. package/dist/cjs/core/targets.cjs +171 -0
  24. package/dist/cjs/core/targets.cjs.map +1 -0
  25. package/dist/cjs/core/targets.d.cts +49 -0
  26. package/dist/cjs/core/types.cjs +22 -11
  27. package/dist/cjs/core/types.cjs.map +1 -1
  28. package/dist/cjs/core/types.d.cts +20 -4
  29. package/dist/cjs/core/validate-project-name.cjs +0 -1
  30. package/dist/cjs/core/validate-project-name.cjs.map +1 -1
  31. package/dist/cjs/frameworks/index.cjs +79 -171
  32. package/dist/cjs/frameworks/index.cjs.map +1 -1
  33. package/dist/cjs/frameworks/index.d.cts +18 -3
  34. package/dist/cjs/scaffold.d.cts +2 -2
  35. package/dist/cjs/templates/base.cjs +15 -99
  36. package/dist/cjs/templates/base.cjs.map +1 -1
  37. package/dist/cjs/templates/base.d.cts +0 -3
  38. package/dist/cjs/types.d.cts +1 -1
  39. package/dist/esm/addons/prisma/index.js +12 -5
  40. package/dist/esm/addons/prisma/index.js.map +1 -1
  41. package/dist/esm/addons/registry.js +4 -2
  42. package/dist/esm/addons/registry.js.map +1 -1
  43. package/dist/esm/addons/validators.js +10 -13
  44. package/dist/esm/addons/validators.js.map +1 -1
  45. package/dist/esm/cli.js +7 -5
  46. package/dist/esm/cli.js.map +1 -1
  47. package/dist/esm/commands/create.d.ts +2 -1
  48. package/dist/esm/commands/create.js +74 -51
  49. package/dist/esm/commands/create.js.map +1 -1
  50. package/dist/esm/core/parse-options.d.ts +19 -4
  51. package/dist/esm/core/parse-options.js +47 -23
  52. package/dist/esm/core/parse-options.js.map +1 -1
  53. package/dist/esm/core/project-config.js +5 -1
  54. package/dist/esm/core/project-config.js.map +1 -1
  55. package/dist/esm/core/resolve-packages.d.ts +1 -4
  56. package/dist/esm/core/resolve-packages.js +35 -65
  57. package/dist/esm/core/resolve-packages.js.map +1 -1
  58. package/dist/esm/core/scaffold-engine.js +18 -29
  59. package/dist/esm/core/scaffold-engine.js.map +1 -1
  60. package/dist/esm/core/targets.d.ts +49 -0
  61. package/dist/esm/core/targets.js +166 -0
  62. package/dist/esm/core/targets.js.map +1 -0
  63. package/dist/esm/core/types.d.ts +20 -4
  64. package/dist/esm/core/types.js +20 -11
  65. package/dist/esm/core/types.js.map +1 -1
  66. package/dist/esm/core/validate-project-name.js +0 -1
  67. package/dist/esm/core/validate-project-name.js.map +1 -1
  68. package/dist/esm/frameworks/index.d.ts +18 -3
  69. package/dist/esm/frameworks/index.js +76 -171
  70. package/dist/esm/frameworks/index.js.map +1 -1
  71. package/dist/esm/scaffold.d.ts +2 -2
  72. package/dist/esm/templates/base.d.ts +0 -3
  73. package/dist/esm/templates/base.js +16 -98
  74. package/dist/esm/templates/base.js.map +1 -1
  75. package/dist/esm/types.d.ts +1 -1
  76. package/package.json +3 -2
  77. package/src/addons/prisma/index.ts +12 -5
  78. package/src/addons/registry.ts +6 -2
  79. package/src/addons/validators.ts +10 -13
  80. package/src/cli.ts +6 -4
  81. package/src/commands/create.ts +92 -26
  82. package/src/core/parse-options.ts +84 -35
  83. package/src/core/project-config.ts +6 -1
  84. package/src/core/resolve-packages.ts +37 -74
  85. package/src/core/scaffold-engine.ts +41 -65
  86. package/src/core/targets.ts +211 -0
  87. package/src/core/types.ts +40 -24
  88. package/src/core/validate-project-name.ts +0 -4
  89. package/src/frameworks/index.ts +80 -178
  90. package/src/scaffold.ts +2 -2
  91. package/src/templates/base.ts +14 -98
  92. package/src/types.ts +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":[],"sources":["../../../src/templates/base.ts"],"sourcesContent":["import type { BootBinding } from \"../addons/types.js\";\nimport type { ScaffoldContext } from \"../core/types.js\";\n\nexport function packageJsonTemplate(\n projectName: string,\n scripts: Record<string, string> = {},\n): string {\n const pkg = {\n name: projectName,\n version: \"1.0.0\",\n private: true,\n type: \"module\",\n imports: {\n \"#src/*\": \"./src/*\",\n },\n scripts: {\n dev: \"run-p dev:server dev:taser\",\n \"dev:server\": \"tsx watch src/index.ts\",\n \"dev:taser\": \"taser watch\",\n start: \"tsx src/index.ts\",\n generate: \"taser generate\",\n build: \"taser generate && tsdown\",\n serve: \"node dist/index.mjs\",\n typecheck: \"tsc --noEmit -p tsconfig.json\",\n ...scripts,\n },\n };\n\n return `${JSON.stringify(pkg, null, 2)}\\n`;\n}\n\nexport function tsdownConfigTemplate(): string {\n return `import { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n entry: ['./src/index.ts'],\n platform: 'node',\n outDir: 'dist',\n clean: true,\n sourcemap: true,\n})\n`;\n}\n\nexport function tsconfigTemplate(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"NodeNext\",\n paths: {\n \"#src/*\": [\"./src/*\"],\n },\n strict: true,\n skipLibCheck: true,\n verbatimModuleSyntax: true,\n isolatedModules: true,\n noEmit: true,\n types: [\"node\"],\n },\n include: [\"src\"],\n },\n null,\n 2,\n )}\\n`;\n}\n\nexport function gitignoreTemplate(): string {\n return `node_modules\ndist\n.DS_Store\n*.log\n.env\nlocal.db\ndrizzle\n`;\n}\n\nexport function contextTemplate(bindings: BootBinding[]): string {\n const imports = bindings.map(\n (binding) => `import { ${binding.factoryName} } from '${binding.importPath}'`,\n );\n\n const bootBody =\n bindings.length > 0\n ? bindings.map((binding) => ` ${binding.key}: ${binding.factoryName}(),`).join(\"\\n\")\n : \"\";\n\n const bootBlock = bindings.length > 0 ? ` boot: () => ({\\n${bootBody}\\n }),` : \"\";\n\n const importBlock = imports.length > 0 ? `${imports.join(\"\\n\")}\\n\\n` : \"\";\n\n return `${importBlock}import { createContext } from '@taserjs/router'\n\nexport const context = createContext({\n${bootBlock}\n request: () => ({\n requestId: crypto.randomUUID(),\n }),\n})\n`;\n}\n\nexport function rootLayoutTemplate(): string {\n return `import { cors } from '@taserjs/router/cors'\n\nimport { t } from '#src/taser.js'\n\nexport const Middleware = t.middleware('/$')\n .use(cors())\n`;\n}\n\nexport function indexRouteTemplate(): string {\n return `import { reply } from '@taserjs/router'\nimport { t } from '#src/taser.js'\n\nconst GET = t.get('/')\n\nexport type RouteContext = typeof GET.$Infer.Context\nexport const Route = GET.handler((_ctx) => {\n return reply.json({ message: 'Welcome to Taser' })\n})\n`;\n}\n\nexport function healthRouteTemplate(ctx: ScaffoldContext): string {\n const lines: string[] = [];\n\n if (ctx.logger) {\n lines.push(\" ctx.logger.info('health check')\");\n }\n\n if (ctx.db) {\n lines.push(\" // ctx.db is available from context boot\");\n }\n\n const body = lines.length > 0 ? `${lines.join(\"\\n\")}\\n` : \"\";\n const ctxArg = lines.length > 0 ? \"(ctx)\" : \"(_ctx)\";\n\n return `import { reply } from '@taserjs/router'\nimport { t } from '#src/taser.js'\n\nconst GET = t.get('/health')\n\nexport type RouteContext = typeof GET.$Infer.Context\nexport const Route = GET.handler(${ctxArg} => {\n${body} return reply.json({ ok: true })\n})\n`;\n}\n\n/** Minimal placeholder until `taser generate` runs. */\nexport function starterManifestTemplate(): string {\n return `/* eslint-disable */\n// Run \\`pnpm generate\\` (taser generate) to replace this file.\nimport { Middleware as RootSplatLayoutImport } from './routes/$.js'\nimport { Route as RootIndexGetRouteImport } from './routes/index.get.js'\nimport { Route as HealthGetRouteImport } from './routes/health.get.js'\n\nexport const routeManifest = {\n layouts: {\n '/$': {\n middlewares: RootSplatLayoutImport,\n },\n },\n routes: {\n '/': {\n GET: {\n layoutChain: ['/$'],\n route: RootIndexGetRouteImport,\n },\n },\n '/health': {\n GET: {\n layoutChain: ['/$'],\n route: HealthGetRouteImport,\n },\n },\n },\n} as const\n\nexport type RoutePathGen = '/' | '/health'\nexport type LayoutIdGen = '/$'\nexport type LayoutTreeGen = {\n '/$': {\n parent: null\n middlewares: typeof RootSplatLayoutImport\n }\n}\nexport type RouteByPathMethodGen = {\n '/': {\n GET: {\n parent: '/$'\n layoutChain: ['/$']\n route: typeof RootIndexGetRouteImport\n }\n }\n '/health': {\n GET: {\n parent: '/$'\n layoutChain: ['/$']\n route: typeof HealthGetRouteImport\n }\n }\n}\nexport type RouteManifest = typeof routeManifest\n\ndeclare module '@taserjs/router' {\n interface RouterRegister {\n RoutePath: RoutePathGen\n LayoutId: LayoutIdGen\n LayoutTree: LayoutTreeGen\n RouteByPathMethod: RouteByPathMethodGen\n }\n}\n`;\n}\n"],"mappings":";AAGA,SAAgB,oBACd,aACA,UAAkC,CAAC,GAC3B;CACR,MAAM,MAAM;EACV,MAAM;EACN,SAAS;EACT,SAAS;EACT,MAAM;EACN,SAAS,EACP,UAAU,UACZ;EACA,SAAS;GACP,KAAK;GACL,cAAc;GACd,aAAa;GACb,OAAO;GACP,UAAU;GACV,OAAO;GACP,OAAO;GACP,WAAW;GACX,GAAG;EACL;CACF;CAEA,OAAO,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,EAAE;AACzC;AAEA,SAAgB,uBAA+B;CAC7C,OAAO;;;;;;;;;;AAUT;AAEA,SAAgB,mBAA2B;CACzC,OAAO,GAAG,KAAK,UACb;EACE,iBAAiB;GACf,QAAQ;GACR,QAAQ;GACR,OAAO,EACL,UAAU,CAAC,SAAS,EACtB;GACA,QAAQ;GACR,cAAc;GACd,sBAAsB;GACtB,iBAAiB;GACjB,QAAQ;GACR,OAAO,CAAC,MAAM;EAChB;EACA,SAAS,CAAC,KAAK;CACjB,GACA,MACA,CACF,EAAE;AACJ;AAEA,SAAgB,oBAA4B;CAC1C,OAAO;;;;;;;;AAQT;AAEA,SAAgB,gBAAgB,UAAiC;CAC/D,MAAM,UAAU,SAAS,KACtB,YAAY,YAAY,QAAQ,YAAY,WAAW,QAAQ,WAAW,EAC7E;CAEA,MAAM,WACJ,SAAS,SAAS,IACd,SAAS,KAAK,YAAY,OAAO,QAAQ,IAAI,IAAI,QAAQ,YAAY,IAAI,CAAC,CAAC,KAAK,IAAI,IACpF;CAEN,MAAM,YAAY,SAAS,SAAS,IAAI,qBAAqB,SAAS,WAAW;CAIjF,OAAO,GAFa,QAAQ,SAAS,IAAI,GAAG,QAAQ,KAAK,IAAI,EAAE,QAAQ,GAEjD;;;EAGtB,UAAU;;;;;;AAMZ;AAEA,SAAgB,qBAA6B;CAC3C,OAAO;;;;;;;AAOT;AAEA,SAAgB,qBAA6B;CAC3C,OAAO;;;;;;;;;;AAUT;AAEA,SAAgB,oBAAoB,KAA8B;CAChE,MAAM,QAAkB,CAAC;CAEzB,IAAI,IAAI,QACN,MAAM,KAAK,mCAAmC;CAGhD,IAAI,IAAI,IACN,MAAM,KAAK,4CAA4C;CAGzD,MAAM,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,KAAK,IAAI,EAAE,MAAM;CAG1D,OAAO;;;;;;mCAFQ,MAAM,SAAS,IAAI,UAAU,SAQJ;EACxC,KAAK;;;AAGP;;AAGA,SAAgB,0BAAkC;CAChD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DT"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../src/templates/base.ts"],"sourcesContent":["import type { BootBinding } from \"../addons/types.js\";\nimport type { ScaffoldContext } from \"../core/types.js\";\n\nexport function packageJsonTemplate(\n projectName: string,\n scripts: Record<string, string> = {},\n): string {\n const pkg = {\n name: projectName,\n version: \"1.0.0\",\n private: true,\n type: \"module\",\n imports: {\n \"#taserjs/router\": \"./src/taser.ts\",\n },\n scripts: {\n dev: \"vite\",\n build: \"vite build\",\n typecheck: \"tsc --noEmit -p tsconfig.json\",\n ...scripts,\n },\n };\n\n return `${JSON.stringify(pkg, null, 2)}\\n`;\n}\n\nexport function tsconfigTemplate(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"NodeNext\",\n paths: {\n \"#taserjs/router\": [\"./src/taser.ts\"],\n },\n strict: true,\n skipLibCheck: true,\n verbatimModuleSyntax: true,\n isolatedModules: true,\n noEmit: true,\n },\n include: [\"src\", \".taser/types/**/*.d.ts\"],\n },\n null,\n 2,\n )}\\n`;\n}\n\nexport function gitignoreTemplate(): string {\n return `node_modules\ndist\n.output\n.taser\n.DS_Store\n*.log\n.env\nlocal.db\ndrizzle\n`;\n}\n\nexport function contextTemplate(bindings: BootBinding[]): string {\n const imports = bindings.map(\n (binding) => `import { ${binding.factoryName} } from '${binding.importPath}'`,\n );\n\n const bootBody =\n bindings.length > 0\n ? bindings.map((binding) => ` ${binding.key}: ${binding.factoryName}(),`).join(\"\\n\")\n : \"\";\n\n const bootBlock = bindings.length > 0 ? ` boot: () => ({\\n${bootBody}\\n }),` : \"\";\n\n const importBlock = imports.length > 0 ? `${imports.join(\"\\n\")}\\n\\n` : \"\";\n\n return `${importBlock}import { createContext } from '@taserjs/router'\n\nexport const context = createContext({\n${bootBlock}\n request: () => ({\n requestId: crypto.randomUUID(),\n }),\n})\n`;\n}\n\nexport function rootLayoutTemplate(): string {\n return `import { cors } from '@taserjs/router/cors'\n\nimport { t } from '#taserjs/router'\n\nexport const Middleware = t.middleware('/$')\n .use(cors())\n`;\n}\n\nexport function indexRouteTemplate(): string {\n return `import { json } from '@taserjs/router/reply'\nimport { t } from '#taserjs/router'\n\nconst GET = t.get('/')\n\nexport type RouteContext = typeof GET.$Infer.Context\nexport const Route = GET.handler((_ctx) => {\n return json({ message: 'Welcome to Taser' })\n})\n`;\n}\n\nexport function healthRouteTemplate(ctx: ScaffoldContext): string {\n const lines: string[] = [];\n\n if (ctx.logger) {\n lines.push(\" ctx.logger.info('health check')\");\n }\n\n if (ctx.db) {\n lines.push(\" // ctx.db is available from context boot\");\n }\n\n const body = lines.length > 0 ? `${lines.join(\"\\n\")}\\n` : \"\";\n const ctxArg = lines.length > 0 ? \"(ctx)\" : \"(_ctx)\";\n\n return `import { json } from '@taserjs/router/reply'\nimport { t } from '#taserjs/router'\n\nconst GET = t.get('/health')\n\nexport type RouteContext = typeof GET.$Infer.Context\nexport const Route = GET.handler(${ctxArg} => {\n${body} return json({ ok: true })\n})\n`;\n}\n"],"mappings":";AAGA,SAAgB,oBACd,aACA,UAAkC,CAAC,GAC3B;CACR,MAAM,MAAM;EACV,MAAM;EACN,SAAS;EACT,SAAS;EACT,MAAM;EACN,SAAS,EACP,mBAAmB,iBACrB;EACA,SAAS;GACP,KAAK;GACL,OAAO;GACP,WAAW;GACX,GAAG;EACL;CACF;CAEA,OAAO,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,EAAE;AACzC;AAEA,SAAgB,mBAA2B;CACzC,OAAO,GAAG,KAAK,UACb;EACE,iBAAiB;GACf,QAAQ;GACR,QAAQ;GACR,OAAO,EACL,mBAAmB,CAAC,gBAAgB,EACtC;GACA,QAAQ;GACR,cAAc;GACd,sBAAsB;GACtB,iBAAiB;GACjB,QAAQ;EACV;EACA,SAAS,CAAC,OAAO,wBAAwB;CAC3C,GACA,MACA,CACF,EAAE;AACJ;AAEA,SAAgB,oBAA4B;CAC1C,OAAO;;;;;;;;;;AAUT;AAEA,SAAgB,gBAAgB,UAAiC;CAC/D,MAAM,UAAU,SAAS,KACtB,YAAY,YAAY,QAAQ,YAAY,WAAW,QAAQ,WAAW,EAC7E;CAEA,MAAM,WACJ,SAAS,SAAS,IACd,SAAS,KAAK,YAAY,OAAO,QAAQ,IAAI,IAAI,QAAQ,YAAY,IAAI,CAAC,CAAC,KAAK,IAAI,IACpF;CAEN,MAAM,YAAY,SAAS,SAAS,IAAI,qBAAqB,SAAS,WAAW;CAIjF,OAAO,GAFa,QAAQ,SAAS,IAAI,GAAG,QAAQ,KAAK,IAAI,EAAE,QAAQ,GAEjD;;;EAGtB,UAAU;;;;;;AAMZ;AAEA,SAAgB,qBAA6B;CAC3C,OAAO;;;;;;;AAOT;AAEA,SAAgB,qBAA6B;CAC3C,OAAO;;;;;;;;;;AAUT;AAEA,SAAgB,oBAAoB,KAA8B;CAChE,MAAM,QAAkB,CAAC;CAEzB,IAAI,IAAI,QACN,MAAM,KAAK,mCAAmC;CAGhD,IAAI,IAAI,IACN,MAAM,KAAK,4CAA4C;CAGzD,MAAM,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,KAAK,IAAI,EAAE,MAAM;CAG1D,OAAO;;;;;;mCAFQ,MAAM,SAAS,IAAI,UAAU,SAQJ;EACxC,KAAK;;;AAGP"}
@@ -1 +1 @@
1
- export type { DbDriver, DbOdm, LoggerId, PackageGroups, ProjectType, ScaffoldOptions, ScaffoldResult, ValidatorId, } from './core/types.js';
1
+ export type { DbDriver, DbOdm, DeployTarget, Framework, LoggerId, PackageGroups, Runtime, ScaffoldOptions, ScaffoldResult, ValidatorId, } from './core/types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-taserjs",
3
- "version": "0.0.6",
3
+ "version": "0.1.1",
4
4
  "description": "Scaffold a new Taser application",
5
5
  "license": "ISC",
6
6
  "repository": {
@@ -31,7 +31,8 @@
31
31
  "publint": "^0.3.22",
32
32
  "typescript": "^5.9.3",
33
33
  "vite": "^8.1.5",
34
- "vitest": "^4.1.10"
34
+ "vitest": "^4.1.10",
35
+ "@taserjs/router-generator": "0.1.1"
35
36
  },
36
37
  "engines": {
37
38
  "node": ">=20.19"
@@ -1,6 +1,13 @@
1
1
  import type { AddonDefinition } from "../types.js";
2
2
  import type { DbDriver } from "../../core/types.js";
3
3
 
4
+ /** Latest Prisma ORM v7 release line — keep all @prisma/* packages on this range. */
5
+ const PRISMA_VERSION = "^7.10.0";
6
+
7
+ function prismaPackage(name: string): string {
8
+ return `${name}@${PRISMA_VERSION}`;
9
+ }
10
+
4
11
  function prismaProvider(driver: DbDriver): string {
5
12
  switch (driver) {
6
13
  case "postgres":
@@ -28,12 +35,12 @@ function envExample(driver: DbDriver): string {
28
35
  function driverPackages(driver: DbDriver): string[] {
29
36
  switch (driver) {
30
37
  case "postgres":
31
- return ["@prisma/adapter-pg", "pg"];
38
+ return [prismaPackage("@prisma/adapter-pg"), "pg"];
32
39
  case "mysql":
33
- return ["@prisma/adapter-mariadb", "mariadb"];
40
+ return [prismaPackage("@prisma/adapter-mariadb"), "mariadb"];
34
41
  case "sqlite":
35
42
  default:
36
- return ["@prisma/adapter-better-sqlite3", "better-sqlite3"];
43
+ return [prismaPackage("@prisma/adapter-better-sqlite3"), "better-sqlite3"];
37
44
  }
38
45
  }
39
46
 
@@ -125,11 +132,11 @@ export const prismaAddon: AddonDefinition = {
125
132
  category: "database",
126
133
  dependencies(ctx) {
127
134
  const driver = ctx.driver ?? "sqlite";
128
- return ["@prisma/client", ...driverPackages(driver)];
135
+ return [prismaPackage("@prisma/client"), ...driverPackages(driver)];
129
136
  },
130
137
  devDependencies(ctx) {
131
138
  const driver = ctx.driver ?? "sqlite";
132
- return ["prisma", ...driverDevPackages(driver)];
139
+ return [prismaPackage("prisma"), ...driverDevPackages(driver)];
133
140
  },
134
141
  scripts() {
135
142
  return {
@@ -12,8 +12,10 @@ import {
12
12
  DB_DRIVERS,
13
13
  DB_ODMS,
14
14
  DEFAULT_DB_DRIVER,
15
+ DEPLOY_TARGETS,
16
+ FRAMEWORKS,
15
17
  LOGGERS,
16
- PROJECT_TYPES,
18
+ RUNTIMES,
17
19
  VALIDATORS,
18
20
  } from "../core/types.js";
19
21
 
@@ -34,7 +36,9 @@ const VALIDATOR_ADDONS = ALL_ADDONS.filter((addon) => addon.category === "valida
34
36
 
35
37
  export function getCapabilitiesCatalog(): CapabilitiesCatalog {
36
38
  return {
37
- types: [...PROJECT_TYPES],
39
+ frameworks: [...FRAMEWORKS],
40
+ deployTargets: [...DEPLOY_TARGETS],
41
+ runtimes: [...RUNTIMES],
38
42
  db: {
39
43
  odms: [...DB_ODMS],
40
44
  drivers: [...DB_DRIVERS],
@@ -8,26 +8,23 @@ export const IMPORT_LINES: Record<ValidatorId, string> = {
8
8
  };
9
9
 
10
10
  export const VALIDATION_BLOCK_TEMPLATE: Record<ValidatorId, string> = {
11
- zod: `, {
12
- query: z.object({ name: z.string().default('Taser') }),
13
- }`,
14
- arktype: `, {
15
- query: type({ 'name?': 'string = "Taser"' }),
16
- }`,
17
- valibot: `, {
18
- query: v.object({ name: v.optional(v.string(), 'Taser') }),
19
- }`,
11
+ zod: `
12
+ .query(z.object({ name: z.string().default('Taser') }))`,
13
+ arktype: `
14
+ .query(type({ 'name?': 'string = "Taser"' }))`,
15
+ valibot: `
16
+ .query(v.object({ name: v.optional(v.string(), 'Taser') }))`,
20
17
  };
21
18
 
22
- const ROUTE_TEMPLATE = (validator: ValidatorId) => `import { reply } from '@taserjs/router'
23
- import { t } from '#src/taser.js'
19
+ const ROUTE_TEMPLATE = (validator: ValidatorId) => `import { json } from '@taserjs/router/reply'
20
+ import { t } from '#taserjs/router'
24
21
  ${IMPORT_LINES[validator]}
25
22
 
26
- const GET = t.get('/'${VALIDATION_BLOCK_TEMPLATE[validator]})
23
+ const GET = t.get('/')${VALIDATION_BLOCK_TEMPLATE[validator]}
27
24
 
28
25
  export type RouteContext = typeof GET.$Infer.Context
29
26
  export const Route = GET.handler((ctx) => {
30
- return reply.json({ message: \`Hello, \${ctx.query.name}!\` })
27
+ return json({ message: \`Hello, \${ctx.query.name}!\` })
31
28
  })
32
29
  `;
33
30
 
package/src/cli.ts CHANGED
@@ -11,11 +11,12 @@ async function main(): Promise<void> {
11
11
  const { values, positionals } = parseArgs({
12
12
  args: process.argv.slice(2),
13
13
  options: {
14
- type: { type: "string", short: "t" },
15
- framework: { type: "string" },
14
+ preset: { type: "string", short: "p" },
16
15
  db: { type: "string" },
17
16
  logger: { type: "string" },
18
17
  validator: { type: "string" },
18
+ framework: { type: "string" },
19
+ runtime: { type: "string" },
19
20
  y: { type: "boolean", short: "y" },
20
21
  noInstall: { type: "boolean" },
21
22
  json: { type: "boolean" },
@@ -25,11 +26,12 @@ async function main(): Promise<void> {
25
26
 
26
27
  const parsed = buildParsedArgsFromCli(
27
28
  {
28
- ...(values.type !== undefined ? { type: values.type } : {}),
29
- ...(values.framework !== undefined ? { framework: values.framework } : {}),
29
+ ...(values.preset !== undefined ? { preset: values.preset } : {}),
30
30
  ...(values.db !== undefined ? { db: values.db } : {}),
31
31
  ...(values.logger !== undefined ? { logger: values.logger } : {}),
32
32
  ...(values.validator !== undefined ? { validator: values.validator } : {}),
33
+ ...(values.framework !== undefined ? { framework: values.framework } : {}),
34
+ ...(values.runtime !== undefined ? { runtime: values.runtime } : {}),
33
35
  ...(values.y !== undefined ? { y: values.y } : {}),
34
36
  ...(values.noInstall !== undefined ? { noInstall: values.noInstall } : {}),
35
37
  ...(values.json !== undefined ? { json: values.json } : {}),
@@ -11,27 +11,30 @@ import {
11
11
  } from "../core/json-output.js";
12
12
  import {
13
13
  parseDbFlag,
14
+ parseFrameworkFlag,
14
15
  parseLoggerFlag,
15
- parseTypeFlag,
16
+ parsePresetFlag,
17
+ parseRuntimeFlag,
16
18
  parseValidatorFlag,
17
19
  type ParsedCreateArgs,
18
20
  resolveScaffoldDefaults,
19
21
  } from "../core/parse-options.js";
20
22
  import { scaffoldProject } from "../core/scaffold-engine.js";
23
+ import { allowedRuntimeOverrides } from "../core/targets.js";
21
24
  import type {
22
25
  DbDriver,
23
26
  DbOdm,
27
+ DeployTarget,
24
28
  LoggerId,
25
- ProjectType,
26
29
  ScaffoldResult,
27
30
  ValidatorId,
28
31
  } from "../core/types.js";
29
- import { DB_DRIVERS, DB_ODMS, LOGGERS, PROJECT_TYPES, VALIDATORS } from "../core/types.js";
32
+ import { DB_DRIVERS, DB_ODMS, DEPLOY_TARGETS, LOGGERS, VALIDATORS } from "../core/types.js";
30
33
  import { validateProjectName } from "../core/validate-project-name.js";
31
34
  import { resolveUserAgent, runScript } from "../core/package-manager.js";
32
35
 
33
- function isProjectType(value: unknown): value is ProjectType {
34
- return typeof value === "string" && (PROJECT_TYPES as readonly string[]).includes(value);
36
+ function isDeployTarget(value: unknown): value is DeployTarget {
37
+ return typeof value === "string" && (DEPLOY_TARGETS as readonly string[]).includes(value);
35
38
  }
36
39
 
37
40
  function isDbOdm(value: unknown): value is DbOdm {
@@ -67,32 +70,81 @@ async function promptInteractiveOptions(args: ParsedCreateArgs): Promise<ParsedC
67
70
  process.exit(0);
68
71
  }
69
72
 
70
- const type =
71
- args.type ??
73
+ const framework =
74
+ args.framework ??
72
75
  (await p.select({
73
- message: "Project type (runtime / framework)",
76
+ message: "Host framework",
74
77
  options: [
75
- { value: "node", label: "Node.js", hint: "default" },
78
+ { value: "none", label: "None", hint: "Taser Fetch (default)" },
79
+ { value: "hono", label: "Hono" },
76
80
  { value: "express", label: "Express" },
77
81
  { value: "fastify", label: "Fastify" },
78
- { value: "hono", label: "Hono" },
79
- { value: "bun", label: "Bun" },
80
- { value: "deno", label: "Deno" },
81
- { value: "aws-lambda", label: "AWS Lambda" },
82
- { value: "cloudflare-workers", label: "Cloudflare Workers" },
83
- { value: "netlify", label: "Netlify" },
84
- { value: "vercel", label: "Vercel" },
85
- { value: "azure-functions", label: "Azure Functions" },
86
- { value: "google-cloud-run", label: "Google Cloud Run" },
87
82
  ],
88
- initialValue: "node",
83
+ initialValue: "none",
89
84
  }));
90
85
 
91
- if (p.isCancel(type) || !isProjectType(type)) {
86
+ if (p.isCancel(framework)) {
92
87
  p.cancel("Scaffold cancelled.");
93
88
  process.exit(0);
94
89
  }
95
90
 
91
+ const DEPLOY_LABELS: Record<DeployTarget, string> = {
92
+ none: "None (Standalone Vite)",
93
+ "node-server": "Node.js server",
94
+ "node-cluster": "Node.js cluster",
95
+ bun: "Bun",
96
+ "deno-server": "Deno",
97
+ "deno-deploy": "Deno Deploy",
98
+ "cloudflare-module": "Cloudflare Workers",
99
+ vercel: "Vercel",
100
+ "aws-lambda": "AWS Lambda",
101
+ netlify: "Netlify",
102
+ };
103
+
104
+ const DEPLOY_HINTS: Partial<Record<DeployTarget, string>> = {
105
+ none: "standalone Vite, no Nitro",
106
+ };
107
+
108
+ const preset =
109
+ args.preset ??
110
+ (await p.select({
111
+ message: "Deployment target",
112
+ // oxlint-disable-next-line oxc/no-map-spread
113
+ options: DEPLOY_TARGETS.map((id) => ({
114
+ value: id,
115
+ label: DEPLOY_LABELS[id],
116
+ ...(DEPLOY_HINTS[id] ? { hint: DEPLOY_HINTS[id] } : {}),
117
+ })),
118
+ initialValue: "node-server",
119
+ }));
120
+
121
+ if (p.isCancel(preset) || !isDeployTarget(preset)) {
122
+ p.cancel("Scaffold cancelled.");
123
+ process.exit(0);
124
+ }
125
+
126
+ let runtime: import("../core/types.js").Runtime | undefined;
127
+ const runtimeOverrides = allowedRuntimeOverrides(preset);
128
+ if (runtimeOverrides.length > 0) {
129
+ const runtimeChoice =
130
+ args.runtime ??
131
+ (await p.select({
132
+ message: "Runtime",
133
+ options: [
134
+ ...runtimeOverrides.map((rt) => ({ value: rt, label: rt })),
135
+ { value: "default", label: `Preset default`, hint: "no override" },
136
+ ],
137
+ initialValue: "default",
138
+ }));
139
+
140
+ if (p.isCancel(runtimeChoice)) {
141
+ p.cancel("Scaffold cancelled.");
142
+ process.exit(0);
143
+ }
144
+
145
+ runtime = runtimeChoice === "default" ? undefined : runtimeChoice;
146
+ }
147
+
96
148
  const dbChoice =
97
149
  args.db ??
98
150
  (await p.select({
@@ -182,7 +234,9 @@ async function promptInteractiveOptions(args: ParsedCreateArgs): Promise<ParsedC
182
234
 
183
235
  return {
184
236
  projectName: String(projectName).trim(),
185
- type,
237
+ framework,
238
+ preset,
239
+ ...(runtime !== undefined ? { runtime } : {}),
186
240
  yes: args.yes,
187
241
  noInstall: args.noInstall,
188
242
  json: args.json,
@@ -207,7 +261,7 @@ export async function runCreateCommand(
207
261
  : {
208
262
  ...args,
209
263
  projectName: args.projectName?.trim(),
210
- type: args.type ?? "node",
264
+ preset: args.preset ?? "node-server",
211
265
  };
212
266
 
213
267
  if (!resolved.projectName) {
@@ -268,8 +322,9 @@ export async function runCreateCommand(
268
322
 
269
323
  export function buildParsedArgsFromCli(
270
324
  values: {
271
- type?: string;
325
+ preset?: string;
272
326
  framework?: string;
327
+ runtime?: string;
273
328
  db?: string;
274
329
  logger?: string;
275
330
  validator?: string;
@@ -289,9 +344,20 @@ export function buildParsedArgsFromCli(
289
344
  args.projectName = positionals[0];
290
345
  }
291
346
 
292
- const typeFlag = values.type ?? values.framework;
293
- if (typeFlag) {
294
- args.type = parseTypeFlag(typeFlag);
347
+ if (values.preset) {
348
+ const parsed = parsePresetFlag(values.preset);
349
+ args.preset = parsed.preset;
350
+ if (parsed.warning && !args.json) {
351
+ console.warn(`warning: ${parsed.warning}`);
352
+ }
353
+ }
354
+
355
+ if (values.framework) {
356
+ args.framework = parseFrameworkFlag(values.framework);
357
+ }
358
+
359
+ if (values.runtime) {
360
+ args.runtime = parseRuntimeFlag(values.runtime);
295
361
  }
296
362
 
297
363
  if (values.db) {
@@ -2,20 +2,32 @@ import {
2
2
  DB_DRIVERS,
3
3
  DB_ODMS,
4
4
  DEFAULT_DB_DRIVER,
5
+ FRAMEWORKS,
5
6
  LOGGERS,
6
- PROJECT_TYPES,
7
+ RUNTIMES,
7
8
  VALIDATORS,
8
9
  type DbDriver,
9
10
  type DbOdm,
11
+ type DeployTarget,
12
+ type Framework,
10
13
  type LoggerId,
11
- type ProjectType,
14
+ type Runtime,
12
15
  type ScaffoldContext,
13
16
  type ValidatorId,
14
17
  } from "./types.js";
18
+ import {
19
+ DEFAULT_DEPLOY,
20
+ isCuratedDeploy,
21
+ resolveDeployEntry,
22
+ validateCombination,
23
+ } from "./targets.js";
15
24
 
16
25
  export type ParsedCreateArgs = {
17
26
  projectName?: string;
18
- type?: ProjectType;
27
+ framework?: Framework;
28
+ /** Deployment target — a Nitro preset id. */
29
+ preset?: string;
30
+ runtime?: Runtime;
19
31
  db?: DbOdm;
20
32
  driver?: DbDriver;
21
33
  logger?: LoggerId;
@@ -25,8 +37,12 @@ export type ParsedCreateArgs = {
25
37
  json: boolean;
26
38
  };
27
39
 
28
- function isProjectType(value: string): value is ProjectType {
29
- return (PROJECT_TYPES as readonly string[]).includes(value);
40
+ function isFramework(value: string): value is Framework {
41
+ return (FRAMEWORKS as readonly string[]).includes(value);
42
+ }
43
+
44
+ function isRuntime(value: string): value is Runtime {
45
+ return (RUNTIMES as readonly string[]).includes(value);
30
46
  }
31
47
 
32
48
  function isDbOdm(value: string): value is DbOdm {
@@ -64,41 +80,33 @@ export function parseDbFlag(value: string): { db: DbOdm; driver: DbDriver } {
64
80
  return { db: odm, driver };
65
81
  }
66
82
 
67
- export function resolveScaffoldDefaults(args: ParsedCreateArgs): ScaffoldContext {
68
- const type = args.type ?? "node";
69
- const db = args.db;
70
- const logger = args.logger;
71
- const validator = args.validator;
72
-
73
- if (!args.projectName) {
74
- throw new Error("Project name is required");
75
- }
76
-
77
- const result: ScaffoldContext = {
78
- projectName: args.projectName.trim(),
79
- targetDir: "",
80
- type,
81
- };
82
-
83
- if (db) {
84
- result.db = db;
85
- result.driver = args.driver ?? DEFAULT_DB_DRIVER;
86
- }
87
-
88
- if (logger) {
89
- result.logger = logger;
83
+ export function parseFrameworkFlag(value: string): Framework {
84
+ if (!isFramework(value)) {
85
+ throw new Error(`Invalid --framework "${value}". Use ${FRAMEWORKS.join(", ")}.`);
90
86
  }
87
+ return value;
88
+ }
91
89
 
92
- if (validator) {
93
- result.validator = validator;
90
+ /**
91
+ * Accepts curated deploy targets verbatim; unknown ids pass through to Nitro.
92
+ * Returns a warning for passthrough ids so callers can surface it.
93
+ */
94
+ export function parsePresetFlag(value: string): { preset: string; warning?: string } {
95
+ if (!isCuratedDeploy(value)) {
96
+ if (!/^[a-z0-9][a-z0-9-_]*$/i.test(value)) {
97
+ throw new Error(`Invalid --preset "${value}".`);
98
+ }
99
+ return {
100
+ preset: value,
101
+ warning: `"${value}" is not a curated Taser deploy target; passing it through to Nitro as-is.`,
102
+ };
94
103
  }
95
-
96
- return result;
104
+ return { preset: value };
97
105
  }
98
106
 
99
- export function parseTypeFlag(value: string): ProjectType {
100
- if (!isProjectType(value)) {
101
- throw new Error(`Invalid --type "${value}". Use ${PROJECT_TYPES.join(", ")}.`);
107
+ export function parseRuntimeFlag(value: string): Runtime {
108
+ if (!isRuntime(value)) {
109
+ throw new Error(`Invalid --runtime "${value}". Use node, bun, or deno.`);
102
110
  }
103
111
  return value;
104
112
  }
@@ -116,3 +124,44 @@ export function parseValidatorFlag(value: string): ValidatorId {
116
124
  }
117
125
  return value;
118
126
  }
127
+
128
+ export function resolveScaffoldDefaults(args: ParsedCreateArgs): ScaffoldContext {
129
+ const framework: Framework = args.framework ?? "none";
130
+ const preset = args.preset ?? DEFAULT_DEPLOY;
131
+
132
+ if (!args.projectName) {
133
+ throw new Error("Project name is required");
134
+ }
135
+
136
+ // Throws with a precise reason when runtime × framework × deploy clash.
137
+ const combination = validateCombination(args.runtime, framework, preset);
138
+ if (!combination.ok) {
139
+ throw new Error(combination.reason);
140
+ }
141
+
142
+ const result: ScaffoldContext = {
143
+ projectName: args.projectName.trim(),
144
+ targetDir: "",
145
+ framework,
146
+ preset: preset as DeployTarget,
147
+ ...(args.runtime !== undefined ? { runtime: args.runtime } : {}),
148
+ };
149
+
150
+ if (args.db) {
151
+ result.db = args.db;
152
+ result.driver = args.driver ?? DEFAULT_DB_DRIVER;
153
+ }
154
+
155
+ if (args.logger) {
156
+ result.logger = args.logger;
157
+ }
158
+
159
+ if (args.validator) {
160
+ result.validator = args.validator;
161
+ }
162
+
163
+ return result;
164
+ }
165
+
166
+ /** Re-exported for CLI layers that need to warn about passthrough presets. */
167
+ export { resolveDeployEntry };
@@ -5,9 +5,14 @@ import type { ScaffoldContext } from "./types.js";
5
5
 
6
6
  export async function writeProjectConfig(root: string, ctx: ScaffoldContext): Promise<void> {
7
7
  const config: Record<string, string> = {
8
- type: ctx.type,
8
+ framework: ctx.framework ?? "none",
9
+ preset: ctx.preset ?? "node-server",
9
10
  };
10
11
 
12
+ if (ctx.runtime) {
13
+ config.runtime = ctx.runtime;
14
+ }
15
+
11
16
  if (ctx.db) {
12
17
  config.db = ctx.db;
13
18
  if (ctx.driver) {