wlmaker 1.2.8 → 1.2.10

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 (2) hide show
  1. package/dist/cli.mjs +494 -34
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli.ts
4
4
  import { createRequire } from "module";
5
5
  import { Command } from "commander";
6
- import chalk5 from "chalk";
6
+ import chalk6 from "chalk";
7
7
 
8
8
  // src/core/create-bloc.ts
9
9
  import * as fs3 from "fs";
@@ -106,17 +106,17 @@ function hasBuildRunner(projectRoot) {
106
106
  return /build_runner/.test(pubspec);
107
107
  }
108
108
  function runBuildRunner(projectRoot) {
109
- return new Promise((resolve3) => {
109
+ return new Promise((resolve4) => {
110
110
  const child = spawn(
111
111
  "dart",
112
112
  ["run", "build_runner", "build", "--delete-conflicting-outputs"],
113
113
  { cwd: projectRoot, stdio: "inherit" }
114
114
  );
115
115
  child.on("close", (code) => {
116
- resolve3();
116
+ resolve4();
117
117
  });
118
118
  child.on("error", () => {
119
- resolve3();
119
+ resolve4();
120
120
  });
121
121
  });
122
122
  }
@@ -956,18 +956,357 @@ async function createUseCase(name, tierInput, options) {
956
956
  }
957
957
 
958
958
  // src/interactive.ts
959
- import * as fs8 from "fs";
959
+ import * as fs10 from "fs";
960
960
  import * as os from "os";
961
- import * as path8 from "path";
961
+ import * as path9 from "path";
962
962
  import * as clack from "@clack/prompts";
963
+ import chalk5 from "chalk";
964
+
965
+ // src/core/create-endpoint.ts
966
+ import * as fs9 from "fs";
967
+ import * as path8 from "path";
963
968
  import chalk4 from "chalk";
969
+ import { pascalCase as pascalCase4, camelCase } from "change-case";
970
+
971
+ // src/core/endpoint-templates.ts
972
+ function entityBoilerplate(pascal) {
973
+ return `class ${pascal}Entity {
974
+ // TODO: Define fields
975
+
976
+ const ${pascal}Entity();
977
+ }
978
+ `;
979
+ }
980
+ function modelBoilerplate(name, pascal) {
981
+ return `import 'package:json_annotation/json_annotation.dart';
982
+ import '../../domain/entities/${name}/${name}_entity.dart';
983
+
984
+ part '${name}_model.g.dart';
985
+
986
+ @JsonSerializable()
987
+ class ${pascal}Model extends ${pascal}Entity {
988
+ const ${pascal}Model() : super();
989
+
990
+ factory ${pascal}Model.fromJson(Map<String, dynamic> json) =>
991
+ _$${pascal}ModelFromJson(json);
992
+
993
+ @override
994
+ Map<String, dynamic> toJson() => _$${pascal}ModelToJson(this);
995
+ }
996
+ `;
997
+ }
998
+ function requestModelBoilerplate(name, pascal) {
999
+ return `import 'package:json_annotation/json_annotation.dart';
1000
+
1001
+ part '${name}_request_model.g.dart';
1002
+
1003
+ @JsonSerializable()
1004
+ class ${pascal}RequestModel {
1005
+ // TODO: Define fields
1006
+
1007
+ ${pascal}RequestModel();
1008
+
1009
+ factory ${pascal}RequestModel.fromJson(Map<String, dynamic> json) =>
1010
+ _$${pascal}RequestModelFromJson(json);
1011
+
1012
+ Map<String, dynamic> toJson() => _$${pascal}RequestModelToJson(this);
1013
+ }
1014
+ `;
1015
+ }
1016
+ function useCaseTemplate2(name, pascal, method, params, returnType, repositoryInterface) {
1017
+ const hasParams = params.length > 0;
1018
+ const paramsClass = hasParams ? `
1019
+ class Params {
1020
+ ${params.map((p) => ` final ${p.type} ${p.name};`).join("\n")}
1021
+ const Params({${params.map((p) => `required this.${p.name}`).join(", ")}});
1022
+ }
1023
+ ` : "";
1024
+ const callParams = hasParams ? "Params params" : "";
1025
+ const callReturn = `Future<${returnType}>`;
1026
+ const args = hasParams ? params.map((p) => `params.${p.name}`).join(", ") : "";
1027
+ return `import 'package:dartz/dartz.dart';
1028
+ import '../../repositories/${repositoryInterface}.dart';
1029
+
1030
+ class ${pascal}UseCase {
1031
+ final ${repositoryInterface} _repository;
1032
+
1033
+ ${pascal}UseCase(this._repository);
1034
+ ${paramsClass}
1035
+ ${callReturn} call(${callParams}) async {
1036
+ return await _repository.${method}(${args});
1037
+ }
1038
+ }
1039
+ `;
1040
+ }
1041
+ function retrofitMethod(methodName, path10, httpMethod, params, returnType) {
1042
+ const httpAnnotation = `@${httpMethod.toUpperCase()}('${path10}')`;
1043
+ const paramList = params.map((p) => {
1044
+ if (p.isPath) return `@Path('${p.name}') ${p.type} ${p.name}`;
1045
+ if (p.isBody) return `@Body() ${p.type} ${p.name}`;
1046
+ if (p.isQuery) return `@Query('${p.name}') ${p.type} ${p.name}`;
1047
+ return `${p.type} ${p.name}`;
1048
+ }).join(", ");
1049
+ return `${httpAnnotation}
1050
+ Future<${returnType}> ${methodName}(${paramList});`;
1051
+ }
1052
+ function datasourceMethod(methodName, returnType, params) {
1053
+ const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
1054
+ return `Future<${returnType}> ${methodName}(${paramList}) async {
1055
+ // TODO: implement ${methodName}
1056
+ throw UnimplementedError();
1057
+ }`;
1058
+ }
1059
+ function repositoryInterfaceMethod(methodName, returnType, params) {
1060
+ const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
1061
+ return `Future<${returnType}> ${methodName}(${paramList});`;
1062
+ }
1063
+ function repositoryImplMethod(methodName, returnType, params, datasourceName) {
1064
+ const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
1065
+ const args = params.map((p) => p.name).join(", ");
1066
+ return `@override
1067
+ Future<${returnType}> ${methodName}(${paramList}) async {
1068
+ return await ${datasourceName}.${methodName}(${args});
1069
+ }`;
1070
+ }
1071
+
1072
+ // src/core/dart-injector.ts
1073
+ import * as fs8 from "fs";
1074
+ function injectMethod(filePath, className, methodCode) {
1075
+ const content = fs8.readFileSync(filePath, "utf8");
1076
+ const classRegex = new RegExp(`class\\s+${className}\\s*[^{]*\\{`);
1077
+ const classMatch = content.match(classRegex);
1078
+ if (!classMatch) {
1079
+ throw new Error(`Class "${className}" not found in ${filePath}`);
1080
+ }
1081
+ const classStart = content.indexOf(classMatch[0]);
1082
+ let braceCount = 0;
1083
+ let classEnd = -1;
1084
+ let foundOpen = false;
1085
+ for (let i = classStart; i < content.length; i++) {
1086
+ if (content[i] === "{") {
1087
+ braceCount++;
1088
+ foundOpen = true;
1089
+ } else if (content[i] === "}") {
1090
+ braceCount--;
1091
+ if (foundOpen && braceCount === 0) {
1092
+ classEnd = i;
1093
+ break;
1094
+ }
1095
+ }
1096
+ }
1097
+ if (classEnd === -1) {
1098
+ throw new Error(`Could not find closing brace for class "${className}" in ${filePath}`);
1099
+ }
1100
+ const methodSignature = methodCode.trim().split("\n")[0].trim();
1101
+ if (content.includes(methodSignature)) {
1102
+ return;
1103
+ }
1104
+ const indentedMethod = methodCode.split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
1105
+ const newContent = content.slice(0, classEnd) + "\n" + indentedMethod + "\n" + content.slice(classEnd);
1106
+ fs8.writeFileSync(filePath, newContent);
1107
+ }
1108
+ function injectImport(filePath, importLine) {
1109
+ const content = fs8.readFileSync(filePath, "utf8");
1110
+ if (content.includes(importLine.trim())) {
1111
+ return;
1112
+ }
1113
+ const importRegex = /^import\s+[^;]+;/gm;
1114
+ const imports = [...content.matchAll(importRegex)];
1115
+ if (imports.length > 0) {
1116
+ const lastImport = imports[imports.length - 1];
1117
+ const insertPos = lastImport.index + lastImport[0].length;
1118
+ const newContent = content.slice(0, insertPos) + "\n" + importLine + content.slice(insertPos);
1119
+ fs8.writeFileSync(filePath, newContent);
1120
+ } else {
1121
+ fs8.writeFileSync(filePath, importLine + "\n\n" + content);
1122
+ }
1123
+ }
1124
+ function injectExport(filePath, exportLine) {
1125
+ let content = "";
1126
+ if (fs8.existsSync(filePath)) {
1127
+ content = fs8.readFileSync(filePath, "utf8");
1128
+ }
1129
+ if (content.includes(exportLine)) {
1130
+ return;
1131
+ }
1132
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
1133
+ lines.push(exportLine);
1134
+ lines.sort();
1135
+ fs8.writeFileSync(filePath, lines.join("\n") + "\n");
1136
+ }
1137
+
1138
+ // src/core/create-endpoint.ts
1139
+ async function createEndpoint(options) {
1140
+ const lib = path8.join(options.projectRoot, "lib");
1141
+ const pascal = pascalCase4(options.useCaseName);
1142
+ const useCasePascal = pascalCase4(options.useCaseName);
1143
+ const useCaseSnake = options.useCaseName;
1144
+ const needsBody = ["POST", "PUT", "PATCH"].includes(options.httpMethod);
1145
+ const domain = extractDomain(options.bffApiFile);
1146
+ const datasourceFile = path8.join(lib, "data", "datasources", `${domain}_rest_datasource.dart`);
1147
+ const datasourceClassName = `${pascalCase4(domain)}RestDataSource`;
1148
+ const repositoryInterfaceFile = path8.join(lib, "domain", "repositories", `${domain}_repository.dart`);
1149
+ const repositoryInterfaceName = `${pascalCase4(domain)}Repository`;
1150
+ const repositoryImplFile = path8.join(lib, "data", "repositories", `${domain}_repository_data.dart`);
1151
+ const repositoryImplClassName = `${pascalCase4(domain)}RepositoryData`;
1152
+ const feature = domain;
1153
+ const pathParams = extractPathParams(options.endpointPath);
1154
+ const methodParams = buildMethodParams(options, pathParams, needsBody);
1155
+ const methodParamsForSignature = methodParams.map((p) => ({ name: p.name, type: p.type }));
1156
+ const returnType = `${pascal}Entity`;
1157
+ const repositoryInterfaceSnake = path8.basename(repositoryInterfaceFile, ".dart");
1158
+ const spinner2 = (msg) => console.log(chalk4.cyan(` \u2192 ${msg}`));
1159
+ spinner2("Generating entity");
1160
+ const entityDir = path8.join(lib, "domain", "entities", feature, useCaseSnake);
1161
+ fs9.mkdirSync(entityDir, { recursive: true });
1162
+ fs9.writeFileSync(
1163
+ path8.join(entityDir, `${useCaseSnake}_entity.dart`),
1164
+ entityBoilerplate(pascal)
1165
+ );
1166
+ spinner2("Generating model");
1167
+ const modelDir = path8.join(lib, "data", "models", feature, useCaseSnake);
1168
+ fs9.mkdirSync(modelDir, { recursive: true });
1169
+ fs9.writeFileSync(
1170
+ path8.join(modelDir, `${useCaseSnake}_model.dart`),
1171
+ modelBoilerplate(useCaseSnake, pascal)
1172
+ );
1173
+ if (needsBody) {
1174
+ spinner2("Generating request model");
1175
+ const reqModelDir = path8.join(lib, "data", "models", feature, useCaseSnake);
1176
+ fs9.mkdirSync(reqModelDir, { recursive: true });
1177
+ fs9.writeFileSync(
1178
+ path8.join(reqModelDir, `${useCaseSnake}_request_model.dart`),
1179
+ requestModelBoilerplate(useCaseSnake, pascal)
1180
+ );
1181
+ }
1182
+ spinner2("Injecting Retrofit method");
1183
+ const bffPath = path8.resolve(options.bffApiFile);
1184
+ if (fs9.existsSync(bffPath)) {
1185
+ const retrofitParams = buildRetrofitParams(options, pathParams, needsBody);
1186
+ const retrofitReturnType = `${pascal}Model`;
1187
+ const method = retrofitMethod(
1188
+ camelCase(options.useCaseName),
1189
+ options.endpointPath,
1190
+ options.httpMethod,
1191
+ retrofitParams,
1192
+ retrofitReturnType
1193
+ );
1194
+ injectMethod(bffPath, extractClassName(bffPath), method);
1195
+ const modelImport = `import 'package:${feature}/data/models/${useCaseSnake}/${useCaseSnake}_model.dart';`;
1196
+ injectImport(bffPath, modelImport);
1197
+ } else {
1198
+ console.log(chalk4.yellow(` \u26A0 BFF API file not found: ${bffPath}`));
1199
+ }
1200
+ spinner2("Injecting datasource method");
1201
+ const dsPath = path8.resolve(datasourceFile);
1202
+ if (fs9.existsSync(dsPath)) {
1203
+ const dsMethod = datasourceMethod(
1204
+ camelCase(options.useCaseName),
1205
+ returnType,
1206
+ methodParamsForSignature
1207
+ );
1208
+ injectMethod(dsPath, datasourceClassName, dsMethod);
1209
+ } else {
1210
+ console.log(chalk4.yellow(` \u26A0 Datasource file not found: ${dsPath}`));
1211
+ }
1212
+ spinner2("Injecting repository interface method");
1213
+ const repoIfacePath = path8.resolve(repositoryInterfaceFile);
1214
+ if (fs9.existsSync(repoIfacePath)) {
1215
+ const ifaceMethod = repositoryInterfaceMethod(
1216
+ camelCase(options.useCaseName),
1217
+ returnType,
1218
+ methodParamsForSignature
1219
+ );
1220
+ injectMethod(repoIfacePath, repositoryInterfaceName, ifaceMethod);
1221
+ const entityImport = `import 'package:${feature}/domain/entities/${useCaseSnake}/${useCaseSnake}_entity.dart';`;
1222
+ injectImport(repoIfacePath, entityImport);
1223
+ } else {
1224
+ console.log(chalk4.yellow(` \u26A0 Repository interface not found: ${repoIfacePath}`));
1225
+ }
1226
+ spinner2("Injecting repository implementation method");
1227
+ const repoImplPath = path8.resolve(repositoryImplFile);
1228
+ if (fs9.existsSync(repoImplPath)) {
1229
+ const dsVarName = camelCase(datasourceClassName);
1230
+ const implMethod = repositoryImplMethod(
1231
+ camelCase(options.useCaseName),
1232
+ returnType,
1233
+ methodParamsForSignature,
1234
+ dsVarName
1235
+ );
1236
+ injectMethod(repoImplPath, repositoryImplClassName, implMethod);
1237
+ } else {
1238
+ console.log(chalk4.yellow(` \u26A0 Repository implementation not found: ${repoImplPath}`));
1239
+ }
1240
+ spinner2("Generating UseCase");
1241
+ const useCaseDir = path8.join(lib, "domain", "usecases", feature);
1242
+ fs9.mkdirSync(useCaseDir, { recursive: true });
1243
+ fs9.writeFileSync(
1244
+ path8.join(useCaseDir, `${useCaseSnake}_usecase.dart`),
1245
+ useCaseTemplate2(
1246
+ useCaseSnake,
1247
+ useCasePascal,
1248
+ camelCase(options.useCaseName),
1249
+ methodParamsForSignature,
1250
+ returnType,
1251
+ repositoryInterfaceSnake
1252
+ )
1253
+ );
1254
+ spinner2("Updating barrel file");
1255
+ const barrelPath = path8.join(useCaseDir, "usecases.dart");
1256
+ const exportLine = `export '${useCaseSnake}_usecase.dart';`;
1257
+ injectExport(barrelPath, exportLine);
1258
+ console.log(chalk4.green(`
1259
+ \u2713 Endpoint "${options.useCaseName}" generated successfully.`));
1260
+ }
1261
+ function extractDomain(bffApiFile) {
1262
+ const baseName = path8.basename(bffApiFile);
1263
+ const match = baseName.match(/^bff_(.+)_api\.dart$/);
1264
+ if (match) return match[1];
1265
+ return baseName.replace(/\.dart$/, "");
1266
+ }
1267
+ function extractPathParams(endpointPath) {
1268
+ const regex = /\{(\w+)\}/g;
1269
+ const params = [];
1270
+ let match;
1271
+ while ((match = regex.exec(endpointPath)) !== null) {
1272
+ params.push({ name: match[1], type: "String" });
1273
+ }
1274
+ return params;
1275
+ }
1276
+ function buildMethodParams(options, pathParams, hasRequestBody) {
1277
+ const params = [...pathParams];
1278
+ if (hasRequestBody) {
1279
+ const reqPascal = pascalCase4(options.useCaseName);
1280
+ params.push({ name: "body", type: `${reqPascal}RequestModel` });
1281
+ }
1282
+ return params;
1283
+ }
1284
+ function buildRetrofitParams(options, pathParams, hasRequestBody) {
1285
+ const params = [];
1286
+ for (const pp of pathParams) {
1287
+ params.push({ ...pp, isPath: true });
1288
+ }
1289
+ if (hasRequestBody) {
1290
+ const reqPascal = pascalCase4(options.useCaseName);
1291
+ params.push({ name: "body", type: `${reqPascal}RequestModel`, isBody: true });
1292
+ }
1293
+ return params;
1294
+ }
1295
+ function extractClassName(filePath) {
1296
+ const content = fs9.readFileSync(filePath, "utf8");
1297
+ const match = content.match(/class\s+(\w+)\s+/);
1298
+ if (!match) throw new Error(`No class found in ${filePath}`);
1299
+ return match[1];
1300
+ }
1301
+
1302
+ // src/interactive.ts
964
1303
  var SNAKE_CASE_REGEX4 = /^[a-z][a-z0-9_]*$/;
965
1304
  async function resolveProject() {
966
1305
  const s = clack.spinner();
967
1306
  s.start("Analyzing current directory...");
968
1307
  const cwdProject = analyzeProject(process.cwd());
969
1308
  if (cwdProject && (cwdProject.hasFreezed || cwdProject.hasBloc)) {
970
- s.stop(`Found ${chalk4.green(cwdProject.projectName)}`);
1309
+ s.stop(`Found ${chalk5.green(cwdProject.projectName)}`);
971
1310
  return cwdProject;
972
1311
  }
973
1312
  s.message("Looking for Melos monorepo...");
@@ -980,8 +1319,8 @@ async function resolveProject() {
980
1319
  }
981
1320
  }
982
1321
  s.message("Scanning for Flutter projects...");
983
- const homeDev = path8.join(os.homedir(), "Development");
984
- if (fs8.existsSync(homeDev)) {
1322
+ const homeDev = path9.join(os.homedir(), "Development");
1323
+ if (fs10.existsSync(homeDev)) {
985
1324
  const projects = discoverProjects(homeDev, 2);
986
1325
  if (projects.length > 0) {
987
1326
  s.stop(`Found ${projects.length} Flutter project(s)`);
@@ -989,12 +1328,12 @@ async function resolveProject() {
989
1328
  }
990
1329
  }
991
1330
  s.stop("No Flutter projects found");
992
- clack.outro(chalk4.red("Could not find any Flutter project with freezed or flutter_bloc."));
1331
+ clack.outro(chalk5.red("Could not find any Flutter project with freezed or flutter_bloc."));
993
1332
  return null;
994
1333
  }
995
1334
  async function selectPackage(projects) {
996
1335
  if (projects.length === 1) {
997
- clack.log.info(`Using ${chalk4.green(projects[0].projectName)}`);
1336
+ clack.log.info(`Using ${chalk5.green(projects[0].projectName)}`);
998
1337
  return projects[0];
999
1338
  }
1000
1339
  const selected = await clack.select({
@@ -1002,7 +1341,7 @@ async function selectPackage(projects) {
1002
1341
  options: projects.map((p) => ({
1003
1342
  value: p,
1004
1343
  label: p.projectName,
1005
- hint: path8.relative(os.homedir(), p.projectRoot)
1344
+ hint: path9.relative(os.homedir(), p.projectRoot)
1006
1345
  }))
1007
1346
  });
1008
1347
  if (clack.isCancel(selected)) {
@@ -1050,13 +1389,13 @@ async function blocFlow(project) {
1050
1389
  clack.cancel("Cancelled");
1051
1390
  return;
1052
1391
  }
1053
- targetDir = path8.resolve(customPath);
1392
+ targetDir = path9.resolve(customPath);
1054
1393
  } else {
1055
- targetDir = path8.join(project.projectRoot, "lib", "features", feature);
1394
+ targetDir = path9.join(project.projectRoot, "lib", "features", feature);
1056
1395
  }
1057
- } else if (fs8.existsSync(path8.join(project.projectRoot, "lib", "bloc"))) {
1058
- targetDir = path8.join(project.projectRoot, "lib", "bloc");
1059
- clack.log.info(`Target: ${chalk4.cyan("lib/bloc/")}`);
1396
+ } else if (fs10.existsSync(path9.join(project.projectRoot, "lib", "bloc"))) {
1397
+ targetDir = path9.join(project.projectRoot, "lib", "bloc");
1398
+ clack.log.info(`Target: ${chalk5.cyan("lib/bloc/")}`);
1060
1399
  } else {
1061
1400
  clack.note(
1062
1401
  "No lib/features/ directory found. Provide a target path manually.",
@@ -1073,7 +1412,7 @@ async function blocFlow(project) {
1073
1412
  clack.cancel("Cancelled");
1074
1413
  return;
1075
1414
  }
1076
- targetDir = path8.resolve(customPath);
1415
+ targetDir = path9.resolve(customPath);
1077
1416
  }
1078
1417
  const defaultRun = project.hasBuildRunner;
1079
1418
  const runBuildRunner2 = await clack.confirm({
@@ -1092,10 +1431,10 @@ async function blocFlow(project) {
1092
1431
  buildRunner: runBuildRunner2
1093
1432
  });
1094
1433
  genSpinner.stop("BLoC generated");
1095
- clack.outro(chalk4.green("Done!"));
1434
+ clack.outro(chalk5.green("Done!"));
1096
1435
  } catch (error) {
1097
1436
  genSpinner.stop("Failed");
1098
- clack.outro(chalk4.red(`Error: ${error}`));
1437
+ clack.outro(chalk5.red(`Error: ${error}`));
1099
1438
  }
1100
1439
  }
1101
1440
  async function widgetFlow() {
@@ -1103,14 +1442,14 @@ async function widgetFlow() {
1103
1442
  const ds = detectDesignSystem(projectRoot);
1104
1443
  if (!ds) {
1105
1444
  clack.outro(
1106
- chalk4.red(
1445
+ chalk5.red(
1107
1446
  "No design system detected. Run this command from a project with wl_design_system/ directory."
1108
1447
  )
1109
1448
  );
1110
1449
  return;
1111
1450
  }
1112
1451
  clack.log.info(
1113
- `Design system: ${chalk4.cyan(path8.relative(projectRoot, ds.componentsDir))}`
1452
+ `Design system: ${chalk5.cyan(path9.relative(projectRoot, ds.componentsDir))}`
1114
1453
  );
1115
1454
  const name = await clack.text({
1116
1455
  message: "Widget name (snake_case, without wl_ prefix)",
@@ -1175,17 +1514,17 @@ async function widgetFlow() {
1175
1514
  genSpinner.stop(`Use-case skipped: ${e}`);
1176
1515
  }
1177
1516
  }
1178
- clack.outro(chalk4.green("Done!"));
1517
+ clack.outro(chalk5.green("Done!"));
1179
1518
  } catch (error) {
1180
1519
  genSpinner.stop("Failed");
1181
- clack.outro(chalk4.red(`Error: ${error}`));
1520
+ clack.outro(chalk5.red(`Error: ${error}`));
1182
1521
  }
1183
1522
  }
1184
1523
  async function useCaseFlow(project) {
1185
1524
  const ds = detectDesignSystem(project.projectRoot);
1186
1525
  if (!ds) {
1187
1526
  clack.outro(
1188
- chalk4.red(
1527
+ chalk5.red(
1189
1528
  "No design system detected. Ensure wl_design_system/ directory exists."
1190
1529
  )
1191
1530
  );
@@ -1193,7 +1532,7 @@ async function useCaseFlow(project) {
1193
1532
  }
1194
1533
  if (!ds.widgetbookDir) {
1195
1534
  clack.outro(
1196
- chalk4.red(
1535
+ chalk5.red(
1197
1536
  "No widgetbook package detected. Ensure apps/widgetbook/ exists."
1198
1537
  )
1199
1538
  );
@@ -1242,14 +1581,119 @@ async function useCaseFlow(project) {
1242
1581
  buildRunner: runBuildRunner2
1243
1582
  });
1244
1583
  genSpinner.stop("Use-case generated");
1245
- clack.outro(chalk4.green("Done!"));
1584
+ clack.outro(chalk5.green("Done!"));
1585
+ } catch (error) {
1586
+ genSpinner.stop("Failed");
1587
+ clack.outro(chalk5.red(`Error: ${error}`));
1588
+ }
1589
+ }
1590
+ async function endpointFlow(project) {
1591
+ const lib = path9.join(project.projectRoot, "lib");
1592
+ const httpMethod = await clack.select({
1593
+ message: "HTTP Method",
1594
+ options: [
1595
+ { value: "GET", label: "GET" },
1596
+ { value: "POST", label: "POST" },
1597
+ { value: "PUT", label: "PUT" },
1598
+ { value: "PATCH", label: "PATCH" },
1599
+ { value: "DELETE", label: "DELETE" }
1600
+ ]
1601
+ });
1602
+ if (clack.isCancel(httpMethod)) {
1603
+ clack.cancel("Cancelled");
1604
+ return;
1605
+ }
1606
+ const endpointPath = await clack.text({
1607
+ message: "Endpoint path (e.g. /products/{id})",
1608
+ placeholder: "/api/users/{id}",
1609
+ validate: (v) => {
1610
+ if (!v.trim()) return "Path is required";
1611
+ }
1612
+ });
1613
+ if (clack.isCancel(endpointPath)) {
1614
+ clack.cancel("Cancelled");
1615
+ return;
1616
+ }
1617
+ const bffDir = path9.join(lib, "data", "api", "bff");
1618
+ let bffApiFile = "";
1619
+ if (fs10.existsSync(bffDir)) {
1620
+ const dartFiles = fs10.readdirSync(bffDir).filter((f) => f.endsWith(".dart")).sort();
1621
+ if (dartFiles.length > 0) {
1622
+ const selected = await clack.select({
1623
+ message: "Select BFF API file",
1624
+ options: dartFiles.map((f) => ({
1625
+ value: path9.join(bffDir, f),
1626
+ label: f
1627
+ }))
1628
+ });
1629
+ if (clack.isCancel(selected)) {
1630
+ clack.cancel("Cancelled");
1631
+ return;
1632
+ }
1633
+ bffApiFile = selected;
1634
+ } else {
1635
+ const customPath = await clack.text({
1636
+ message: "BFF API file path (no .dart files found in bff/)",
1637
+ placeholder: "lib/data/api/bff/my_api.dart",
1638
+ validate: (v) => {
1639
+ if (!v.trim()) return "Path is required";
1640
+ }
1641
+ });
1642
+ if (clack.isCancel(customPath)) {
1643
+ clack.cancel("Cancelled");
1644
+ return;
1645
+ }
1646
+ bffApiFile = path9.resolve(customPath);
1647
+ }
1648
+ } else {
1649
+ const customPath = await clack.text({
1650
+ message: "BFF API file path (bff/ dir not found)",
1651
+ placeholder: "lib/data/api/bff/my_api.dart",
1652
+ validate: (v) => {
1653
+ if (!v.trim()) return "Path is required";
1654
+ }
1655
+ });
1656
+ if (clack.isCancel(customPath)) {
1657
+ clack.cancel("Cancelled");
1658
+ return;
1659
+ }
1660
+ bffApiFile = path9.resolve(customPath);
1661
+ }
1662
+ const methodLower = httpMethod.toLowerCase();
1663
+ const pathSegments = endpointPath.replace(/^\//, "").split("/").filter((s) => !s.startsWith("{"));
1664
+ const inferredName = pathSegments.length > 0 ? `${methodLower}_${pathSegments.join("_")}` : `${methodLower}_data`;
1665
+ const useCaseName = await clack.text({
1666
+ message: "UseCase name (snake_case)",
1667
+ placeholder: inferredName,
1668
+ initialValue: inferredName,
1669
+ validate: (v) => {
1670
+ if (!v.trim()) return "Name is required";
1671
+ if (!SNAKE_CASE_REGEX4.test(v)) return "Must be snake_case";
1672
+ }
1673
+ });
1674
+ if (clack.isCancel(useCaseName)) {
1675
+ clack.cancel("Cancelled");
1676
+ return;
1677
+ }
1678
+ const genSpinner = clack.spinner();
1679
+ genSpinner.start("Generating endpoint stack...");
1680
+ try {
1681
+ await createEndpoint({
1682
+ projectRoot: project.projectRoot,
1683
+ httpMethod,
1684
+ endpointPath,
1685
+ bffApiFile,
1686
+ useCaseName
1687
+ });
1688
+ genSpinner.stop("Endpoint generated");
1689
+ clack.outro(chalk5.green("Done!"));
1246
1690
  } catch (error) {
1247
1691
  genSpinner.stop("Failed");
1248
- clack.outro(chalk4.red(`Error: ${error}`));
1692
+ clack.outro(chalk5.red(`Error: ${error}`));
1249
1693
  }
1250
1694
  }
1251
1695
  async function interactiveMode() {
1252
- clack.intro(chalk4.bgCyan(chalk4.black(" wlmaker ")));
1696
+ clack.intro(chalk5.bgCyan(chalk5.black(" wlmaker ")));
1253
1697
  const createType = await clack.select({
1254
1698
  message: "What do you want to create?",
1255
1699
  options: [
@@ -1259,6 +1703,11 @@ async function interactiveMode() {
1259
1703
  value: "usecase",
1260
1704
  label: "Widgetbook Use-Case",
1261
1705
  hint: "Component showcase"
1706
+ },
1707
+ {
1708
+ value: "endpoint",
1709
+ label: "Endpoint",
1710
+ hint: "BFF Clean Architecture stack"
1262
1711
  }
1263
1712
  ]
1264
1713
  });
@@ -1283,6 +1732,12 @@ async function interactiveMode() {
1283
1732
  await useCaseFlow(project);
1284
1733
  break;
1285
1734
  }
1735
+ case "endpoint": {
1736
+ const project = await resolveProject();
1737
+ if (!project) return;
1738
+ await endpointFlow(project);
1739
+ break;
1740
+ }
1286
1741
  }
1287
1742
  }
1288
1743
 
@@ -1302,7 +1757,7 @@ program.command("bloc").description("Create a new BLoC with Freezed sealed class
1302
1757
  try {
1303
1758
  await createBloc(name, options);
1304
1759
  } catch (error) {
1305
- console.error(chalk5.red(`Error: ${error}`));
1760
+ console.error(chalk6.red(`Error: ${error}`));
1306
1761
  process.exit(1);
1307
1762
  }
1308
1763
  }
@@ -1319,12 +1774,12 @@ program.command("widget").description("Create a new widget in the design system"
1319
1774
  projectRoot: options.dir,
1320
1775
  buildRunner: false
1321
1776
  });
1322
- console.log(chalk5.green("Widgetbook use-case created"));
1777
+ console.log(chalk6.green("Widgetbook use-case created"));
1323
1778
  } catch {
1324
- console.log(chalk5.yellow("Use-case skipped (may already exist)"));
1779
+ console.log(chalk6.yellow("Use-case skipped (may already exist)"));
1325
1780
  }
1326
1781
  } catch (error) {
1327
- console.error(chalk5.red(`Error: ${error}`));
1782
+ console.error(chalk6.red(`Error: ${error}`));
1328
1783
  process.exit(1);
1329
1784
  }
1330
1785
  }
@@ -1337,11 +1792,16 @@ program.command("usecase").description("Create a Widgetbook use-case for an exis
1337
1792
  buildRunner: options.buildRunner
1338
1793
  });
1339
1794
  } catch (error) {
1340
- console.error(chalk5.red(`Error: ${error}`));
1795
+ console.error(chalk6.red(`Error: ${error}`));
1341
1796
  process.exit(1);
1342
1797
  }
1343
1798
  }
1344
1799
  );
1800
+ program.command("endpoint").description("Generate Clean Architecture stack for a BFF endpoint").action(async () => {
1801
+ const project = await resolveProject();
1802
+ if (!project) return;
1803
+ await endpointFlow(project);
1804
+ });
1345
1805
  program.action(async () => {
1346
1806
  await interactiveMode();
1347
1807
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wlmaker",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "description": "Create Flutter BLoCs with Freezed sealed classes from the terminal",
5
5
  "keywords": [
6
6
  "flutter",