@deepstorm/cli 0.9.3 → 0.10.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/dist/cli.js CHANGED
@@ -6150,6 +6150,10 @@ function parseInitArgs(raw) {
6150
6150
  if (raw.ai && typeof raw.ai === "string") opts.ai = raw.ai;
6151
6151
  if (raw["ui-lib"] && typeof raw["ui-lib"] === "string") opts.uiLib = raw["ui-lib"];
6152
6152
  if (raw["css"] && typeof raw["css"] === "string") opts.cssFramework = raw["css"];
6153
+ if (raw.nodejsOrm && typeof raw.nodejsOrm === "string") opts.nodejsOrm = raw.nodejsOrm;
6154
+ if (raw["nodejs-orm"] && typeof raw["nodejs-orm"] === "string") opts.nodejsOrm = raw["nodejs-orm"];
6155
+ if (raw.nodejsAi && typeof raw.nodejsAi === "string") opts.nodejsAi = raw.nodejsAi;
6156
+ if (raw["nodejs-ai"] && typeof raw["nodejs-ai"] === "string") opts.nodejsAi = raw["nodejs-ai"];
6153
6157
  return opts;
6154
6158
  }
6155
6159
  var CONTEXT_MD_NAME = "context.md";
@@ -6249,10 +6253,15 @@ function initClaudeMd(targetDir, opts) {
6249
6253
  if (opts.cssFramework) techLines.push(`- **CSS \u65B9\u6848**\uFF1A${opts.cssFramework}`);
6250
6254
  }
6251
6255
  if (opts.backend) {
6252
- techLines.push(`- **\u540E\u7AEF**\uFF1A${opts.backend}`);
6253
- if (opts.orm) techLines.push(`- **ORM**\uFF1A${opts.orm}`);
6254
- if (opts.migration) techLines.push(`- **\u6570\u636E\u5E93\u8FC1\u79FB**\uFF1A${opts.migration}`);
6255
- if (opts.ai) techLines.push(`- **AI \u6846\u67B6**\uFF1A${opts.ai}`);
6256
+ techLines.push(`- **\u540E\u7AEF**\uFF1A${opts.backend === "nodejs" ? "Node.js (NestJS)" : opts.backend}`);
6257
+ if (opts.backend === "nodejs") {
6258
+ if (opts.nodejsOrm) techLines.push(`- **ORM**\uFF1A${opts.nodejsOrm}`);
6259
+ if (opts.nodejsAi) techLines.push(`- **AI \u6846\u67B6**\uFF1A${opts.nodejsAi}`);
6260
+ } else {
6261
+ if (opts.orm) techLines.push(`- **ORM**\uFF1A${opts.orm}`);
6262
+ if (opts.migration) techLines.push(`- **\u6570\u636E\u5E93\u8FC1\u79FB**\uFF1A${opts.migration}`);
6263
+ if (opts.ai) techLines.push(`- **AI \u6846\u67B6**\uFF1A${opts.ai}`);
6264
+ }
6256
6265
  }
6257
6266
  if (techLines.length > 0) {
6258
6267
  lines.push("## \u6280\u672F\u6808", "");
@@ -6275,9 +6284,14 @@ function writeInitTechStack(baseDir, opts) {
6275
6284
  if (opts.backend) {
6276
6285
  config["reef.techs"] = opts.frontend ? "frontend,backend" : "backend";
6277
6286
  config["reef.backend.language"] = opts.backend;
6278
- if (opts.orm) config["reef.backend.java.orm"] = opts.orm;
6279
- if (opts.migration) config["reef.backend.java.dbMigration"] = opts.migration;
6280
- if (opts.ai) config["reef.backend.java.ai"] = opts.ai;
6287
+ if (opts.backend === "nodejs") {
6288
+ if (opts.nodejsOrm) config["reef.backend.nodejs.orm"] = opts.nodejsOrm;
6289
+ if (opts.nodejsAi) config["reef.backend.nodejs.ai"] = opts.nodejsAi;
6290
+ } else {
6291
+ if (opts.orm) config["reef.backend.java.orm"] = opts.orm;
6292
+ if (opts.migration) config["reef.backend.java.dbMigration"] = opts.migration;
6293
+ if (opts.ai) config["reef.backend.java.ai"] = opts.ai;
6294
+ }
6281
6295
  }
6282
6296
  if (Object.keys(config).length === 0) return;
6283
6297
  const nested = buildInitNestedConfig(config);
@@ -6304,7 +6318,7 @@ function buildInitNestedConfig(flat) {
6304
6318
  return result;
6305
6319
  }
6306
6320
  function registerInitCommand(program2, registry2) {
6307
- program2.command("init").description("\u521D\u59CB\u5316\u9879\u76EE\u811A\u624B\u67B6 \u2014 \u9009\u62E9\u6280\u672F\u6808\uFF0C\u751F\u6210\u9879\u76EE\u9AA8\u67B6").option("--name <name>", "\u9879\u76EE\u540D\u79F0").option("--frontend <framework>", "\u524D\u7AEF\u6846\u67B6\uFF08angular\uFF09").option("--backend <language>", "\u540E\u7AEF\u8BED\u8A00\uFF08java\uFF09").option("--output <dir>", "\u8F93\u51FA\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4E3A\u5F53\u524D\u76EE\u5F55\uFF09").option("--ui-lib <lib>", "UI \u5E93\uFF08primeng\uFF09").option("--css <framework>", "CSS \u6846\u67B6\uFF08tailwind\uFF09").option("--orm <orm>", "ORM \u6846\u67B6\uFF08hibernate\uFF09").option("--migration <tool>", "\u6570\u636E\u5E93\u8FC1\u79FB\u5DE5\u5177\uFF08liquibase\uFF09").option("--ai <framework>", "AI \u6846\u67B6\uFF08spring-ai\uFF09").action(async (options) => {
6321
+ program2.command("init").description("\u521D\u59CB\u5316\u9879\u76EE\u811A\u624B\u67B6 \u2014 \u9009\u62E9\u6280\u672F\u6808\uFF0C\u751F\u6210\u9879\u76EE\u9AA8\u67B6").option("--name <name>", "\u9879\u76EE\u540D\u79F0").option("--frontend <framework>", "\u524D\u7AEF\u6846\u67B6\uFF08angular\uFF09").option("--backend <language>", "\u540E\u7AEF\u8BED\u8A00\uFF08java / nodejs\uFF09").option("--output <dir>", "\u8F93\u51FA\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4E3A\u5F53\u524D\u76EE\u5F55\uFF09").option("--ui-lib <lib>", "UI \u5E93\uFF08primeng\uFF09").option("--css <framework>", "CSS \u6846\u67B6\uFF08tailwind\uFF09").option("--orm <orm>", "ORM \u6846\u67B6\uFF08hibernate\uFF09").option("--migration <tool>", "\u6570\u636E\u5E93\u8FC1\u79FB\u5DE5\u5177\uFF08liquibase\uFF09").option("--ai <framework>", "AI \u6846\u67B6\uFF08spring-ai\uFF09").option("--nodejs-orm <orm>", "Node.js ORM\uFF08prisma\uFF09").option("--nodejs-ai <framework>", "Node.js AI \u6846\u67B6\uFF08claude-agent-sdk\uFF09").action(async (options) => {
6308
6322
  const args = parseInitArgs(options);
6309
6323
  const targetDir = options.output || process.cwd();
6310
6324
  if (args.projectName && (args.frontend || args.backend)) {
@@ -6439,6 +6453,7 @@ async function runInteractiveMode(targetDir) {
6439
6453
  message: "\u9009\u62E9\u540E\u7AEF\u6846\u67B6\uFF1A",
6440
6454
  options: [
6441
6455
  { value: "java", label: "Java (Spring Boot)" },
6456
+ { value: "nodejs", label: "Node.js (NestJS)" },
6442
6457
  { value: "none", label: "\u4E0D\u9700\u8981\u540E\u7AEF" }
6443
6458
  ]
6444
6459
  });
@@ -6487,6 +6502,34 @@ async function runInteractiveMode(targetDir) {
6487
6502
  }
6488
6503
  aiChoice = aiResult;
6489
6504
  }
6505
+ let nodejsOrm;
6506
+ let nodejsAi;
6507
+ if (backendChoice === "nodejs") {
6508
+ const ormResult = await select2({
6509
+ message: "\u9009\u62E9 ORM \u6846\u67B6\uFF1A",
6510
+ options: [
6511
+ { value: "prisma", label: "Prisma" },
6512
+ { value: "none", label: "\u65E0" }
6513
+ ]
6514
+ });
6515
+ if (isCancel8(ormResult)) {
6516
+ outro2("\u5DF2\u53D6\u6D88");
6517
+ return;
6518
+ }
6519
+ nodejsOrm = ormResult;
6520
+ const aiResult = await select2({
6521
+ message: "\u9009\u62E9 AI \u6846\u67B6\uFF1A",
6522
+ options: [
6523
+ { value: "claude-agent-sdk", label: "Claude Agent SDK" },
6524
+ { value: "none", label: "\u65E0" }
6525
+ ]
6526
+ });
6527
+ if (isCancel8(aiResult)) {
6528
+ outro2("\u5DF2\u53D6\u6D88");
6529
+ return;
6530
+ }
6531
+ nodejsAi = aiResult;
6532
+ }
6490
6533
  if ((!frontendChoice || frontendChoice === "none") && (!backendChoice || backendChoice === "none")) {
6491
6534
  console.error("\u274C \u81F3\u5C11\u9700\u8981\u9009\u62E9\u524D\u7AEF\u6216\u540E\u7AEF\u4E4B\u4E00");
6492
6535
  outro2("\u521D\u59CB\u5316\u5931\u8D25");
@@ -6500,7 +6543,9 @@ async function runInteractiveMode(targetDir) {
6500
6543
  cssFramework: cssFramework && cssFramework !== "none" ? cssFramework : void 0,
6501
6544
  orm: orm && orm !== "none" ? orm : void 0,
6502
6545
  migration: migration && migration !== "none" ? migration : void 0,
6503
- ai: aiChoice && aiChoice !== "none" ? aiChoice : void 0
6546
+ ai: aiChoice && aiChoice !== "none" ? aiChoice : void 0,
6547
+ nodejsOrm: nodejsOrm && nodejsOrm !== "none" ? nodejsOrm : void 0,
6548
+ nodejsAi: nodejsAi && nodejsAi !== "none" ? nodejsAi : void 0
6504
6549
  };
6505
6550
  await runInit(targetDir, opts);
6506
6551
  outro2("\u2705 \u9879\u76EE\u5DF2\u751F\u6210\uFF01");
@@ -6521,15 +6566,20 @@ async function runInit(baseDir, opts) {
6521
6566
  }
6522
6567
  const ctx = buildContext(opts);
6523
6568
  const hasFrontend = opts.frontend === "angular";
6524
- const hasBackend = opts.backend === "java";
6569
+ const hasJavaBackend = opts.backend === "java";
6570
+ const hasNodeBackend = opts.backend === "nodejs";
6571
+ const hasBackend = hasJavaBackend || hasNodeBackend;
6525
6572
  try {
6526
6573
  if (hasFrontend) {
6527
6574
  renderAngularTemplate(projectDir, ctx);
6528
6575
  }
6529
- if (hasBackend) {
6576
+ if (hasJavaBackend) {
6530
6577
  renderJavaTemplate(projectDir, ctx);
6531
6578
  }
6532
- renderCommonFiles(projectDir, ctx);
6579
+ if (hasNodeBackend) {
6580
+ renderNestJSTemplate(projectDir, ctx, opts);
6581
+ }
6582
+ renderCommonFiles(projectDir, ctx, opts);
6533
6583
  console.log(`
6534
6584
  \u2714 \u9879\u76EE\u5DF2\u521B\u5EFA: ${projectDir}`);
6535
6585
  console.log("\n\u4E0B\u4E00\u6B65:");
@@ -6537,9 +6587,10 @@ async function runInit(baseDir, opts) {
6537
6587
  console.log(` cd ${opts.projectName}`);
6538
6588
  }
6539
6589
  if (opts.frontend) console.log(` pnpm install # \u5B89\u88C5\u524D\u7AEF\u4F9D\u8D56`);
6540
- if (opts.backend) console.log(` ./gradlew build # \u6784\u5EFA\u540E\u7AEF`);
6590
+ if (hasJavaBackend) console.log(` ./gradlew build # \u6784\u5EFA\u540E\u7AEF`);
6591
+ if (hasNodeBackend) console.log(` pnpm install # \u5B89\u88C5\u4F9D\u8D56`);
6541
6592
  console.log();
6542
- printProjectTree(projectDir, hasFrontend, hasBackend);
6593
+ printProjectTree(projectDir, hasFrontend, hasJavaBackend, hasNodeBackend);
6543
6594
  if (inPlace) {
6544
6595
  initClaudeMd(baseDir, opts);
6545
6596
  }
@@ -7037,7 +7088,371 @@ set CLASSPATH=%APP_HOME%/gradle/wrapper/gradle-wrapper.jar
7037
7088
  @endlocal & exit /b %ERRORLEVEL%
7038
7089
  `);
7039
7090
  }
7040
- function renderCommonFiles(projectDir, ctx) {
7091
+ function renderNestJSTemplate(projectDir, ctx, opts) {
7092
+ const hasPrisma = opts.nodejsOrm === "prisma";
7093
+ const hasAgentSdk = opts.nodejsAi === "claude-agent-sdk";
7094
+ const pkgDeps = {
7095
+ "@nestjs/common": "^11.0.0",
7096
+ "@nestjs/core": "^11.0.0",
7097
+ "@nestjs/platform-express": "^11.0.0",
7098
+ "@nestjs/config": "^4.0.0",
7099
+ "@nestjs/swagger": "^9.0.0",
7100
+ "class-validator": "^0.14.0",
7101
+ "class-transformer": "^0.5.0",
7102
+ "reflect-metadata": "^0.2.0",
7103
+ rxjs: "^7.8.0"
7104
+ };
7105
+ const devDeps = {
7106
+ "@nestjs/cli": "^11.0.0",
7107
+ "@nestjs/schematics": "^11.0.0",
7108
+ "@nestjs/testing": "^11.0.0",
7109
+ "@types/express": "^5.0.0",
7110
+ "@types/jest": "^29.5.0",
7111
+ "@types/node": "^22.0.0",
7112
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
7113
+ "@typescript-eslint/parser": "^8.0.0",
7114
+ eslint: "^9.0.0",
7115
+ "eslint-config-prettier": "^10.0.0",
7116
+ "eslint-plugin-prettier": "^5.0.0",
7117
+ jest: "^29.7.0",
7118
+ prettier: "^3.4.0",
7119
+ "source-map-support": "^0.5.0",
7120
+ "ts-jest": "^29.0.0",
7121
+ "ts-loader": "^9.5.0",
7122
+ "ts-node": "^10.9.0",
7123
+ "tsconfig-paths": "^4.2.0",
7124
+ typescript: "^5.7.0"
7125
+ };
7126
+ if (hasPrisma) {
7127
+ pkgDeps["@prisma/client"] = "^6.0.0";
7128
+ devDeps.prisma = "^6.0.0";
7129
+ }
7130
+ if (hasAgentSdk) {
7131
+ pkgDeps["@anthropic-ai/sdk"] = "^0.40.0";
7132
+ }
7133
+ writeTemplate(projectDir, "package.json", JSON.stringify({
7134
+ name: ctx.packageName,
7135
+ version: "0.1.0",
7136
+ description: "",
7137
+ private: true,
7138
+ scripts: {
7139
+ build: "nest build",
7140
+ format: 'prettier --write "src/**/*.ts"',
7141
+ start: "nest start",
7142
+ "start:dev": "nest start --watch",
7143
+ "start:prod": "node dist/main",
7144
+ lint: 'eslint "{src,test}/**/*.ts" --fix',
7145
+ test: "jest",
7146
+ "test:watch": "jest --watch",
7147
+ "test:cov": "jest --coverage",
7148
+ "test:e2e": "jest --config ./test/jest-e2e.json"
7149
+ },
7150
+ dependencies: pkgDeps,
7151
+ devDependencies: devDeps,
7152
+ jest: {
7153
+ moduleFileExtensions: ["js", "json", "ts"],
7154
+ rootDir: "src",
7155
+ testRegex: ".*\\.spec\\.ts$",
7156
+ transform: { "^.+\\.(t|j)s$": "ts-jest" },
7157
+ collectCoverageFrom: ["**/*.(t|j)s"],
7158
+ coverageDirectory: "../coverage",
7159
+ testEnvironment: "node"
7160
+ }
7161
+ }, null, 2));
7162
+ writeTemplate(projectDir, "tsconfig.json", JSON.stringify({
7163
+ compilerOptions: {
7164
+ module: "commonjs",
7165
+ declaration: true,
7166
+ removeComments: true,
7167
+ emitDecoratorMetadata: true,
7168
+ experimentalDecorators: true,
7169
+ allowSyntheticDefaultImports: true,
7170
+ target: "ES2023",
7171
+ sourceMap: true,
7172
+ outDir: "./dist",
7173
+ baseUrl: "./",
7174
+ incremental: true,
7175
+ skipLibCheck: true,
7176
+ strict: true,
7177
+ strictNullChecks: true,
7178
+ noImplicitAny: true,
7179
+ strictBindCallApply: true,
7180
+ forceConsistentCasingInFileNames: true,
7181
+ noFallthroughCasesInSwitch: true,
7182
+ esModuleInterop: true,
7183
+ resolveJsonModule: true
7184
+ }
7185
+ }, null, 2));
7186
+ writeTemplate(projectDir, "nest-cli.json", JSON.stringify({
7187
+ $schema: "https://json.schemastore.org/nest-cli",
7188
+ collection: "@nestjs/schematics",
7189
+ sourceRoot: "src",
7190
+ compilerOptions: { deleteOutDir: true }
7191
+ }, null, 2));
7192
+ writeTemplate(projectDir, ".prettierrc", JSON.stringify({
7193
+ singleQuote: true,
7194
+ trailingComma: "all",
7195
+ tabWidth: 2,
7196
+ semi: true,
7197
+ printWidth: 120
7198
+ }, null, 2));
7199
+ ensureDir2(path2.join(projectDir, "src"));
7200
+ writeTemplate(projectDir, "src/main.ts", [
7201
+ "import { NestFactory } from '@nestjs/core';",
7202
+ "import { ValidationPipe, Logger } from '@nestjs/common';",
7203
+ "import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';",
7204
+ "import { AppModule } from './app.module';",
7205
+ "",
7206
+ "async function bootstrap() {",
7207
+ " const app = await NestFactory.create(AppModule);",
7208
+ " const logger = new Logger('Bootstrap');",
7209
+ "",
7210
+ " app.setGlobalPrefix('api/v1');",
7211
+ "",
7212
+ " app.useGlobalPipes(",
7213
+ " new ValidationPipe({",
7214
+ " whitelist: true,",
7215
+ " forbidNonWhitelisted: true,",
7216
+ " transform: true,",
7217
+ " }),",
7218
+ " );",
7219
+ "",
7220
+ " const config = new DocumentBuilder()",
7221
+ ` .setTitle('${ctx.projectName}')`,
7222
+ " .setDescription('API documentation')",
7223
+ " .setVersion('1.0')",
7224
+ " .addBearerAuth()",
7225
+ " .build();",
7226
+ " const document = SwaggerModule.createDocument(app, config);",
7227
+ " SwaggerModule.setup('api/docs', app, document);",
7228
+ "",
7229
+ " app.enableCors({",
7230
+ " origin: process.env.CORS_ORIGIN || '*',",
7231
+ " methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',",
7232
+ " credentials: true,",
7233
+ " });",
7234
+ "",
7235
+ " const port = process.env.PORT || 3000;",
7236
+ " await app.listen(port);",
7237
+ " logger.log(`Application running on http://localhost:${port}`);",
7238
+ " logger.log(`Swagger docs at http://localhost:${port}/api/docs`);",
7239
+ "}",
7240
+ "bootstrap();",
7241
+ ""
7242
+ ].join("\n"));
7243
+ const appModuleContent = [
7244
+ "import { Module } from '@nestjs/common';",
7245
+ "import { ConfigModule } from '@nestjs/config';",
7246
+ "import { AppController } from './app.controller';",
7247
+ "import { AppService } from './app.service';"
7248
+ ];
7249
+ const appModuleImports = [
7250
+ " ConfigModule.forRoot({",
7251
+ " isGlobal: true,",
7252
+ " }),"
7253
+ ];
7254
+ if (hasPrisma) {
7255
+ appModuleContent.push("import { PrismaModule } from './prisma/prisma.module';");
7256
+ appModuleImports.push(" PrismaModule,");
7257
+ }
7258
+ if (hasAgentSdk) {
7259
+ appModuleContent.push("import { AgentModule } from './agent/agent.module';");
7260
+ appModuleImports.push(" AgentModule,");
7261
+ }
7262
+ appModuleContent.push("");
7263
+ appModuleContent.push("/**");
7264
+ appModuleContent.push(" * \u5E94\u7528\u6839\u6A21\u5757");
7265
+ appModuleContent.push(" */");
7266
+ appModuleContent.push("@Module({");
7267
+ appModuleContent.push(" imports: [");
7268
+ appModuleContent.push(appModuleImports.join("\n"));
7269
+ appModuleContent.push(" ],");
7270
+ appModuleContent.push(" controllers: [AppController],");
7271
+ appModuleContent.push(" providers: [AppService],");
7272
+ appModuleContent.push("})");
7273
+ appModuleContent.push("export class AppModule {}");
7274
+ writeTemplate(projectDir, "src/app.module.ts", appModuleContent.join("\n"));
7275
+ writeTemplate(projectDir, "src/app.controller.ts", [
7276
+ "import { Controller, Get } from '@nestjs/common';",
7277
+ "import { ApiTags, ApiOperation } from '@nestjs/swagger';",
7278
+ "import { AppService } from './app.service';",
7279
+ "",
7280
+ "@ApiTags('Health')",
7281
+ "@Controller()",
7282
+ "export class AppController {",
7283
+ " constructor(private readonly appService: AppService) {}",
7284
+ "",
7285
+ " @Get()",
7286
+ " @ApiOperation({ summary: '\u5065\u5EB7\u68C0\u67E5' })",
7287
+ " getHello(): string {",
7288
+ " return this.appService.getHello();",
7289
+ " }",
7290
+ "}",
7291
+ ""
7292
+ ].join("\n"));
7293
+ writeTemplate(projectDir, "src/app.service.ts", [
7294
+ "import { Injectable } from '@nestjs/common';",
7295
+ "",
7296
+ "@Injectable()",
7297
+ "export class AppService {",
7298
+ " getHello(): string {",
7299
+ ` return 'Hello from ${ctx.projectName}!';`,
7300
+ " }",
7301
+ "}",
7302
+ ""
7303
+ ].join("\n"));
7304
+ if (hasPrisma) {
7305
+ ensureDir2(path2.join(projectDir, "prisma"));
7306
+ writeTemplate(projectDir, "prisma/schema.prisma", [
7307
+ "generator client {",
7308
+ ' provider = "prisma-client-js"',
7309
+ "}",
7310
+ "",
7311
+ "datasource db {",
7312
+ ' provider = "postgresql"',
7313
+ ' url = env("DATABASE_URL")',
7314
+ "}",
7315
+ "",
7316
+ "/// \u7528\u6237",
7317
+ "model User {",
7318
+ " id Int @id @default(autoincrement())",
7319
+ " name String",
7320
+ " email String @unique",
7321
+ " active Boolean @default(true)",
7322
+ " createdAt DateTime @default(now())",
7323
+ " updatedAt DateTime @updatedAt",
7324
+ "",
7325
+ ' @@map("users")',
7326
+ "}",
7327
+ ""
7328
+ ].join("\n"));
7329
+ ensureDir2(path2.join(projectDir, "src/prisma"));
7330
+ writeTemplate(projectDir, "src/prisma/prisma.module.ts", [
7331
+ "import { Global, Module } from '@nestjs/common';",
7332
+ "import { PrismaService } from './prisma.service';",
7333
+ "",
7334
+ "@Global()",
7335
+ "@Module({",
7336
+ " providers: [PrismaService],",
7337
+ " exports: [PrismaService],",
7338
+ "})",
7339
+ "export class PrismaModule {}",
7340
+ ""
7341
+ ].join("\n"));
7342
+ writeTemplate(projectDir, "src/prisma/prisma.service.ts", [
7343
+ "import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';",
7344
+ "import { PrismaClient } from '@prisma/client';",
7345
+ "",
7346
+ "@Injectable()",
7347
+ "export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {",
7348
+ " private readonly logger = new Logger(PrismaService.name);",
7349
+ "",
7350
+ " async onModuleInit() {",
7351
+ " await this.$connect();",
7352
+ " this.logger.log('Database connected');",
7353
+ " }",
7354
+ "",
7355
+ " async onModuleDestroy() {",
7356
+ " await this.$disconnect();",
7357
+ " this.logger.log('Database disconnected');",
7358
+ " }",
7359
+ "}",
7360
+ ""
7361
+ ].join("\n"));
7362
+ }
7363
+ if (hasAgentSdk) {
7364
+ ensureDir2(path2.join(projectDir, "src/agent/tools"));
7365
+ writeTemplate(projectDir, "src/agent/agent.module.ts", [
7366
+ "import { Module } from '@nestjs/common';",
7367
+ "import { AgentService } from './agent.service';",
7368
+ "",
7369
+ "@Module({",
7370
+ " providers: [AgentService],",
7371
+ " exports: [AgentService],",
7372
+ "})",
7373
+ "export class AgentModule {}",
7374
+ ""
7375
+ ].join("\n"));
7376
+ writeTemplate(projectDir, "src/agent/agent.service.ts", [
7377
+ "import { Injectable, Logger } from '@nestjs/common';",
7378
+ "import { ConfigService } from '@nestjs/config';",
7379
+ "",
7380
+ "/**",
7381
+ " * Claude Agent \u670D\u52A1",
7382
+ " *",
7383
+ " * \u5C01\u88C5 Claude Agent SDK\uFF0C\u63D0\u4F9B Agent \u4EA4\u4E92\u80FD\u529B\u3002",
7384
+ " * \u4F7F\u7528\u524D\u9700\u5728 .env \u4E2D\u914D\u7F6E ANTHROPIC_API_KEY\u3002",
7385
+ " */",
7386
+ "@Injectable()",
7387
+ "export class AgentService {",
7388
+ " private readonly logger = new Logger(AgentService.name);",
7389
+ "",
7390
+ " constructor(private readonly configService: ConfigService) {",
7391
+ " const apiKey = this.configService.get<string>('ANTHROPIC_API_KEY');",
7392
+ " if (!apiKey) {",
7393
+ " this.logger.warn('ANTHROPIC_API_KEY is not configured');",
7394
+ " }",
7395
+ " }",
7396
+ "",
7397
+ " async ask(question: string): Promise<string> {",
7398
+ ' this.logger.debug(`Agent ask: "${question}"`);',
7399
+ " return 'Agent response placeholder -- configure ANTHROPIC_API_KEY to enable';",
7400
+ " }",
7401
+ "}",
7402
+ ""
7403
+ ].join("\n"));
7404
+ writeTemplate(projectDir, "src/agent/tools/example.tool.ts", [
7405
+ "import { Injectable, Logger } from '@nestjs/common';",
7406
+ "",
7407
+ "export interface ToolDefinition {",
7408
+ " name: string;",
7409
+ " description: string;",
7410
+ " input_schema: Record<string, unknown>;",
7411
+ " execute: (input: Record<string, unknown>) => Promise<unknown>;",
7412
+ "}",
7413
+ "",
7414
+ "@Injectable()",
7415
+ "export class ExampleTool implements ToolDefinition {",
7416
+ " private readonly logger = new Logger(ExampleTool.name);",
7417
+ "",
7418
+ " name = 'get_current_time';",
7419
+ " description = '\u83B7\u53D6\u5F53\u524D\u65E5\u671F\u548C\u65F6\u95F4';",
7420
+ " input_schema = {",
7421
+ " type: 'object',",
7422
+ " properties: {",
7423
+ " timezone: {",
7424
+ " type: 'string',",
7425
+ " description: '\u65F6\u533A\uFF08\u53EF\u9009\uFF0C\u9ED8\u8BA4 UTC\uFF09',",
7426
+ " enum: ['UTC', 'Asia/Shanghai', 'America/New_York'],",
7427
+ " },",
7428
+ " },",
7429
+ " };",
7430
+ "",
7431
+ " async execute(input: Record<string, unknown>): Promise<unknown> {",
7432
+ " try {",
7433
+ " this.logger.debug(`Tool execute: get_current_time with input: ${JSON.stringify(input)}`);",
7434
+ " const now = new Date();",
7435
+ " return {",
7436
+ " success: true,",
7437
+ " data: {",
7438
+ " iso: now.toISOString(),",
7439
+ " timestamp: now.getTime(),",
7440
+ " },",
7441
+ " };",
7442
+ " } catch (error) {",
7443
+ " this.logger.error(`Tool execution failed: ${error}`);",
7444
+ " return {",
7445
+ " success: false,",
7446
+ " error: error instanceof Error ? error.message : 'Unknown error',",
7447
+ " };",
7448
+ " }",
7449
+ " }",
7450
+ "}",
7451
+ ""
7452
+ ].join("\n"));
7453
+ }
7454
+ }
7455
+ function renderCommonFiles(projectDir, ctx, opts) {
7041
7456
  writeTemplate(projectDir, ".gitignore", `node_modules/
7042
7457
  .gradle/
7043
7458
  build/
@@ -7052,7 +7467,21 @@ __pycache__/
7052
7467
  .vscode/
7053
7468
  *.iml
7054
7469
  `);
7055
- writeEnvFile(projectDir, `# \u6570\u636E\u5E93\u914D\u7F6E
7470
+ const hasNodeBackend = opts?.backend === "nodejs";
7471
+ if (hasNodeBackend) {
7472
+ writeEnvFile(projectDir, `# \u6570\u636E\u5E93\u914D\u7F6E
7473
+ DATABASE_URL=postgresql://user:password@localhost:5432/${ctx.packageName}
7474
+
7475
+ # Claude Agent SDK
7476
+ ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx
7477
+ AGENT_MODEL=claude-sonnet-5
7478
+
7479
+ # Server
7480
+ PORT=3000
7481
+ CORS_ORIGIN=http://localhost:4200
7482
+ `);
7483
+ } else {
7484
+ writeEnvFile(projectDir, `# \u6570\u636E\u5E93\u914D\u7F6E
7056
7485
  DB_URL=jdbc:postgresql://localhost:5432/${ctx.packageName}
7057
7486
  DB_USERNAME=postgres
7058
7487
  DB_PASSWORD=change-me
@@ -7060,15 +7489,26 @@ DB_PASSWORD=change-me
7060
7489
  # API Keys
7061
7490
  OPENAI_API_KEY=sk-your-key-here
7062
7491
  `);
7063
- const readme = generateReadme(ctx);
7492
+ }
7493
+ const readme = generateReadme(ctx, opts);
7064
7494
  writeTemplate(projectDir, "README.md", readme);
7065
7495
  }
7066
- function generateReadme(ctx) {
7496
+ function generateReadme(ctx, opts) {
7497
+ const hasNodeBackend = opts?.backend === "nodejs";
7498
+ const hasPrisma = opts?.nodejsOrm === "prisma";
7499
+ const hasAgentSdk = opts?.nodejsAi === "claude-agent-sdk";
7067
7500
  const lines = [];
7068
7501
  lines.push(`# ${ctx.projectName}`, "");
7069
7502
  lines.push("## \u6280\u672F\u6808", "");
7070
7503
  if (ctx.frontend) lines.push("- **\u524D\u7AEF\uFF1A** Angular + TypeScript");
7071
- if (ctx.backend) lines.push("- **\u540E\u7AEF\uFF1A** Java 21 + Spring Boot 3.x + Gradle");
7504
+ if (hasNodeBackend) {
7505
+ const nodeDeps = ["NestJS + TypeScript"];
7506
+ if (hasPrisma) nodeDeps.push("Prisma");
7507
+ if (hasAgentSdk) nodeDeps.push("Claude Agent SDK");
7508
+ lines.push(`- **\u540E\u7AEF\uFF1A** ${nodeDeps.join(" + ")}`);
7509
+ } else if (ctx.backend) {
7510
+ lines.push("- **\u540E\u7AEF\uFF1A** Java 21 + Spring Boot 3.x + Gradle");
7511
+ }
7072
7512
  lines.push("");
7073
7513
  lines.push("## \u5FEB\u901F\u5F00\u59CB", "");
7074
7514
  if (ctx.frontend) {
@@ -7078,7 +7518,13 @@ function generateReadme(ctx) {
7078
7518
  lines.push("pnpm start");
7079
7519
  lines.push("```", "");
7080
7520
  }
7081
- if (ctx.backend) {
7521
+ if (hasNodeBackend) {
7522
+ lines.push("### \u540E\u7AEF", "");
7523
+ lines.push("```bash");
7524
+ lines.push("pnpm install");
7525
+ lines.push("pnpm start:dev");
7526
+ lines.push("```", "");
7527
+ } else if (ctx.backend) {
7082
7528
  lines.push("### \u540E\u7AEF", "");
7083
7529
  lines.push("```bash");
7084
7530
  lines.push("./gradlew build");
@@ -7087,7 +7533,13 @@ function generateReadme(ctx) {
7087
7533
  }
7088
7534
  lines.push("## \u9879\u76EE\u7ED3\u6784", "");
7089
7535
  lines.push("```");
7090
- if (ctx.frontend && ctx.backend) {
7536
+ if (ctx.frontend && hasNodeBackend) {
7537
+ lines.push("\u251C\u2500\u2500 src/ # NestJS + Angular");
7538
+ lines.push("\u251C\u2500\u2500 angular.json # Angular \u914D\u7F6E");
7539
+ lines.push("\u251C\u2500\u2500 nest-cli.json # NestJS \u914D\u7F6E");
7540
+ lines.push("\u251C\u2500\u2500 tsconfig.json");
7541
+ lines.push("\u2514\u2500\u2500 package.json");
7542
+ } else if (ctx.frontend && ctx.backend) {
7091
7543
  lines.push("\u251C\u2500\u2500 src/main/web/ # Angular \u524D\u7AEF");
7092
7544
  lines.push("\u251C\u2500\u2500 src/main/java/ # Java \u540E\u7AEF");
7093
7545
  lines.push("\u251C\u2500\u2500 src/main/resources/ # \u914D\u7F6E");
@@ -7096,6 +7548,11 @@ function generateReadme(ctx) {
7096
7548
  } else if (ctx.frontend) {
7097
7549
  lines.push("\u251C\u2500\u2500 src/main/web/ # Angular \u524D\u7AEF");
7098
7550
  lines.push("\u2514\u2500\u2500 angular.json # Angular \u914D\u7F6E");
7551
+ } else if (hasNodeBackend) {
7552
+ lines.push("\u251C\u2500\u2500 src/ # NestJS \u540E\u7AEF");
7553
+ lines.push("\u251C\u2500\u2500 nest-cli.json # NestJS \u914D\u7F6E");
7554
+ lines.push("\u251C\u2500\u2500 tsconfig.json");
7555
+ lines.push("\u2514\u2500\u2500 package.json");
7099
7556
  } else if (ctx.backend) {
7100
7557
  lines.push("\u251C\u2500\u2500 src/main/java/ # Java \u540E\u7AEF");
7101
7558
  lines.push("\u2514\u2500\u2500 build.gradle.kts # Gradle \u6784\u5EFA");
@@ -7127,10 +7584,10 @@ function writeEnvFile(baseDir, content) {
7127
7584
  fs2.writeFileSync(envPath, content, "utf-8");
7128
7585
  }
7129
7586
  }
7130
- function printProjectTree(projectDir, hasFrontend, hasBackend) {
7587
+ function printProjectTree(projectDir, hasFrontend, hasJavaBackend, hasNodeBackend = false) {
7131
7588
  console.log("\u76EE\u5F55\u7ED3\u6784:");
7132
7589
  console.log(` ${path2.basename(projectDir)}/`);
7133
- if (hasFrontend && hasBackend) {
7590
+ if (hasFrontend && hasJavaBackend) {
7134
7591
  console.log(" \u251C\u2500\u2500 src/");
7135
7592
  console.log(" \u2502 \u251C\u2500\u2500 main/");
7136
7593
  console.log(" \u2502 \u2502 \u251C\u2500\u2500 web/ # Angular \u524D\u7AEF");
@@ -7142,11 +7599,25 @@ function printProjectTree(projectDir, hasFrontend, hasBackend) {
7142
7599
  console.log(" \u251C\u2500\u2500 build.gradle.kts");
7143
7600
  console.log(" \u251C\u2500\u2500 angular.json");
7144
7601
  console.log(" \u2514\u2500\u2500 package.json");
7602
+ } else if (hasFrontend && hasNodeBackend) {
7603
+ console.log(" \u251C\u2500\u2500 src/");
7604
+ console.log(" \u2502 \u251C\u2500\u2500 app.controller.ts");
7605
+ console.log(" \u2502 \u251C\u2500\u2500 app.module.ts");
7606
+ console.log(" \u2502 \u251C\u2500\u2500 app.service.ts");
7607
+ console.log(" \u2502 \u251C\u2500\u2500 main.ts");
7608
+ console.log(" \u2502 \u251C\u2500\u2500 prisma/ # Prisma ORM");
7609
+ console.log(" \u2502 \u2514\u2500\u2500 agent/ # Claude Agent SDK");
7610
+ console.log(" \u251C\u2500\u2500 prisma/");
7611
+ console.log(" \u2502 \u2514\u2500\u2500 schema.prisma");
7612
+ console.log(" \u251C\u2500\u2500 nest-cli.json");
7613
+ console.log(" \u251C\u2500\u2500 tsconfig.json");
7614
+ console.log(" \u251C\u2500\u2500 angular.json");
7615
+ console.log(" \u2514\u2500\u2500 package.json");
7145
7616
  } else if (hasFrontend) {
7146
7617
  console.log(" \u251C\u2500\u2500 src/main/web/ # Angular \u524D\u7AEF");
7147
7618
  console.log(" \u251C\u2500\u2500 angular.json");
7148
7619
  console.log(" \u2514\u2500\u2500 package.json");
7149
- } else if (hasBackend) {
7620
+ } else if (hasJavaBackend) {
7150
7621
  console.log(" \u251C\u2500\u2500 src/");
7151
7622
  console.log(" \u2502 \u251C\u2500\u2500 main/java/");
7152
7623
  console.log(" \u2502 \u251C\u2500\u2500 main/resources/");
@@ -7154,6 +7625,19 @@ function printProjectTree(projectDir, hasFrontend, hasBackend) {
7154
7625
  console.log(" \u2502 \u2514\u2500\u2500 test/resources/");
7155
7626
  console.log(" \u251C\u2500\u2500 build.gradle.kts");
7156
7627
  console.log(" \u2514\u2500\u2500 settings.gradle.kts");
7628
+ } else if (hasNodeBackend) {
7629
+ console.log(" \u251C\u2500\u2500 src/");
7630
+ console.log(" \u2502 \u251C\u2500\u2500 app.controller.ts");
7631
+ console.log(" \u2502 \u251C\u2500\u2500 app.module.ts");
7632
+ console.log(" \u2502 \u251C\u2500\u2500 app.service.ts");
7633
+ console.log(" \u2502 \u251C\u2500\u2500 main.ts");
7634
+ console.log(" \u2502 \u251C\u2500\u2500 prisma/ # Prisma ORM");
7635
+ console.log(" \u2502 \u2514\u2500\u2500 agent/ # Claude Agent SDK");
7636
+ console.log(" \u251C\u2500\u2500 prisma/");
7637
+ console.log(" \u2502 \u2514\u2500\u2500 schema.prisma");
7638
+ console.log(" \u251C\u2500\u2500 nest-cli.json");
7639
+ console.log(" \u251C\u2500\u2500 tsconfig.json");
7640
+ console.log(" \u2514\u2500\u2500 package.json");
7157
7641
  }
7158
7642
  }
7159
7643