wlmaker 1.2.10 → 1.2.12

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 +146 -92
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -977,20 +977,26 @@ function entityBoilerplate(pascal) {
977
977
  }
978
978
  `;
979
979
  }
980
- function modelBoilerplate(name, pascal) {
981
- return `import 'package:json_annotation/json_annotation.dart';
982
- import '../../domain/entities/${name}/${name}_entity.dart';
980
+ function modelBoilerplate(name, pascal, projectName) {
981
+ return `import 'package:${projectName}/core.dart';
982
+ import 'package:json_annotation/json_annotation.dart';
983
983
 
984
984
  part '${name}_model.g.dart';
985
985
 
986
- @JsonSerializable()
987
- class ${pascal}Model extends ${pascal}Entity {
988
- const ${pascal}Model() : super();
986
+ @JsonSerializable(createToJson: true)
987
+ class ${pascal}Model {
988
+ // TODO: Define constructor parameters and fields
989
+
990
+ const ${pascal}Model();
989
991
 
990
992
  factory ${pascal}Model.fromJson(Map<String, dynamic> json) =>
991
993
  _$${pascal}ModelFromJson(json);
992
994
 
993
- @override
995
+ ${pascal}Entity toEntity() {
996
+ // TODO: Implement toEntity mapping
997
+ throw UnimplementedError();
998
+ }
999
+
994
1000
  Map<String, dynamic> toJson() => _$${pascal}ModelToJson(this);
995
1001
  }
996
1002
  `;
@@ -1000,11 +1006,11 @@ function requestModelBoilerplate(name, pascal) {
1000
1006
 
1001
1007
  part '${name}_request_model.g.dart';
1002
1008
 
1003
- @JsonSerializable()
1009
+ @JsonSerializable(createToJson: true)
1004
1010
  class ${pascal}RequestModel {
1005
- // TODO: Define fields
1011
+ // TODO: Define constructor parameters and fields
1006
1012
 
1007
- ${pascal}RequestModel();
1013
+ const ${pascal}RequestModel();
1008
1014
 
1009
1015
  factory ${pascal}RequestModel.fromJson(Map<String, dynamic> json) =>
1010
1016
  _$${pascal}RequestModelFromJson(json);
@@ -1060,12 +1066,13 @@ function repositoryInterfaceMethod(methodName, returnType, params) {
1060
1066
  const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
1061
1067
  return `Future<${returnType}> ${methodName}(${paramList});`;
1062
1068
  }
1063
- function repositoryImplMethod(methodName, returnType, params, datasourceName) {
1069
+ function repositoryImplMethod(methodName, returnType, modelType, params, datasourceName) {
1064
1070
  const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
1065
1071
  const args = params.map((p) => p.name).join(", ");
1066
1072
  return `@override
1067
1073
  Future<${returnType}> ${methodName}(${paramList}) async {
1068
- return await ${datasourceName}.${methodName}(${args});
1074
+ final model = await ${datasourceName}.${methodName}(${args});
1075
+ return model.toEntity();
1069
1076
  }`;
1070
1077
  }
1071
1078
 
@@ -1105,22 +1112,6 @@ function injectMethod(filePath, className, methodCode) {
1105
1112
  const newContent = content.slice(0, classEnd) + "\n" + indentedMethod + "\n" + content.slice(classEnd);
1106
1113
  fs8.writeFileSync(filePath, newContent);
1107
1114
  }
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
1115
  function injectExport(filePath, exportLine) {
1125
1116
  let content = "";
1126
1117
  if (fs8.existsSync(filePath)) {
@@ -1153,26 +1144,27 @@ async function createEndpoint(options) {
1153
1144
  const pathParams = extractPathParams(options.endpointPath);
1154
1145
  const methodParams = buildMethodParams(options, pathParams, needsBody);
1155
1146
  const methodParamsForSignature = methodParams.map((p) => ({ name: p.name, type: p.type }));
1156
- const returnType = `${pascal}Entity`;
1147
+ const modelType = `${pascal}Model`;
1148
+ const entityType = `${pascal}Entity`;
1157
1149
  const repositoryInterfaceSnake = path8.basename(repositoryInterfaceFile, ".dart");
1158
1150
  const spinner2 = (msg) => console.log(chalk4.cyan(` \u2192 ${msg}`));
1159
1151
  spinner2("Generating entity");
1160
- const entityDir = path8.join(lib, "domain", "entities", feature, useCaseSnake);
1152
+ const entityDir = path8.join(lib, "domain", "entities", feature);
1161
1153
  fs9.mkdirSync(entityDir, { recursive: true });
1162
1154
  fs9.writeFileSync(
1163
1155
  path8.join(entityDir, `${useCaseSnake}_entity.dart`),
1164
1156
  entityBoilerplate(pascal)
1165
1157
  );
1166
1158
  spinner2("Generating model");
1167
- const modelDir = path8.join(lib, "data", "models", feature, useCaseSnake);
1159
+ const modelDir = path8.join(lib, "data", "models", feature);
1168
1160
  fs9.mkdirSync(modelDir, { recursive: true });
1169
1161
  fs9.writeFileSync(
1170
1162
  path8.join(modelDir, `${useCaseSnake}_model.dart`),
1171
- modelBoilerplate(useCaseSnake, pascal)
1163
+ modelBoilerplate(useCaseSnake, pascal, options.projectName)
1172
1164
  );
1173
1165
  if (needsBody) {
1174
1166
  spinner2("Generating request model");
1175
- const reqModelDir = path8.join(lib, "data", "models", feature, useCaseSnake);
1167
+ const reqModelDir = path8.join(lib, "data", "models", feature);
1176
1168
  fs9.mkdirSync(reqModelDir, { recursive: true });
1177
1169
  fs9.writeFileSync(
1178
1170
  path8.join(reqModelDir, `${useCaseSnake}_request_model.dart`),
@@ -1183,17 +1175,14 @@ async function createEndpoint(options) {
1183
1175
  const bffPath = path8.resolve(options.bffApiFile);
1184
1176
  if (fs9.existsSync(bffPath)) {
1185
1177
  const retrofitParams = buildRetrofitParams(options, pathParams, needsBody);
1186
- const retrofitReturnType = `${pascal}Model`;
1187
1178
  const method = retrofitMethod(
1188
1179
  camelCase(options.useCaseName),
1189
1180
  options.endpointPath,
1190
1181
  options.httpMethod,
1191
1182
  retrofitParams,
1192
- retrofitReturnType
1183
+ modelType
1193
1184
  );
1194
1185
  injectMethod(bffPath, extractClassName(bffPath), method);
1195
- const modelImport = `import 'package:${feature}/data/models/${useCaseSnake}/${useCaseSnake}_model.dart';`;
1196
- injectImport(bffPath, modelImport);
1197
1186
  } else {
1198
1187
  console.log(chalk4.yellow(` \u26A0 BFF API file not found: ${bffPath}`));
1199
1188
  }
@@ -1202,7 +1191,7 @@ async function createEndpoint(options) {
1202
1191
  if (fs9.existsSync(dsPath)) {
1203
1192
  const dsMethod = datasourceMethod(
1204
1193
  camelCase(options.useCaseName),
1205
- returnType,
1194
+ modelType,
1206
1195
  methodParamsForSignature
1207
1196
  );
1208
1197
  injectMethod(dsPath, datasourceClassName, dsMethod);
@@ -1214,12 +1203,10 @@ async function createEndpoint(options) {
1214
1203
  if (fs9.existsSync(repoIfacePath)) {
1215
1204
  const ifaceMethod = repositoryInterfaceMethod(
1216
1205
  camelCase(options.useCaseName),
1217
- returnType,
1206
+ entityType,
1218
1207
  methodParamsForSignature
1219
1208
  );
1220
1209
  injectMethod(repoIfacePath, repositoryInterfaceName, ifaceMethod);
1221
- const entityImport = `import 'package:${feature}/domain/entities/${useCaseSnake}/${useCaseSnake}_entity.dart';`;
1222
- injectImport(repoIfacePath, entityImport);
1223
1210
  } else {
1224
1211
  console.log(chalk4.yellow(` \u26A0 Repository interface not found: ${repoIfacePath}`));
1225
1212
  }
@@ -1229,7 +1216,8 @@ async function createEndpoint(options) {
1229
1216
  const dsVarName = camelCase(datasourceClassName);
1230
1217
  const implMethod = repositoryImplMethod(
1231
1218
  camelCase(options.useCaseName),
1232
- returnType,
1219
+ entityType,
1220
+ modelType,
1233
1221
  methodParamsForSignature,
1234
1222
  dsVarName
1235
1223
  );
@@ -1247,14 +1235,32 @@ async function createEndpoint(options) {
1247
1235
  useCasePascal,
1248
1236
  camelCase(options.useCaseName),
1249
1237
  methodParamsForSignature,
1250
- returnType,
1238
+ entityType,
1251
1239
  repositoryInterfaceSnake
1252
1240
  )
1253
1241
  );
1254
- spinner2("Updating barrel file");
1255
- const barrelPath = path8.join(useCaseDir, "usecases.dart");
1256
- const exportLine = `export '${useCaseSnake}_usecase.dart';`;
1257
- injectExport(barrelPath, exportLine);
1242
+ spinner2("Updating barrel files");
1243
+ const entityBarrel = path8.join(lib, "domain", "entities", feature, `${feature}.dart`);
1244
+ injectExport(entityBarrel, `export '${useCaseSnake}_entity.dart';`);
1245
+ injectExport(
1246
+ path8.join(lib, "domain", "entities", "entities.dart"),
1247
+ `export '${feature}/${feature}.dart';`
1248
+ );
1249
+ const modelBarrel = path8.join(lib, "data", "models", feature, `${feature}.dart`);
1250
+ injectExport(modelBarrel, `export '${useCaseSnake}_model.dart';`);
1251
+ if (needsBody) {
1252
+ injectExport(modelBarrel, `export '${useCaseSnake}_request_model.dart';`);
1253
+ }
1254
+ injectExport(
1255
+ path8.join(lib, "data", "models", "models.dart"),
1256
+ `export '${feature}/${feature}.dart';`
1257
+ );
1258
+ const useCaseBarrel = path8.join(useCaseDir, `${feature}.dart`);
1259
+ injectExport(useCaseBarrel, `export '${useCaseSnake}_usecase.dart';`);
1260
+ injectExport(
1261
+ path8.join(lib, "domain", "usecases", "usecases.dart"),
1262
+ `export '${feature}/${feature}.dart';`
1263
+ );
1258
1264
  console.log(chalk4.green(`
1259
1265
  \u2713 Endpoint "${options.useCaseName}" generated successfully.`));
1260
1266
  }
@@ -1587,8 +1593,86 @@ async function useCaseFlow(project) {
1587
1593
  clack.outro(chalk5.red(`Error: ${error}`));
1588
1594
  }
1589
1595
  }
1590
- async function endpointFlow(project) {
1596
+ function findBffFiles(dir) {
1597
+ const results = [];
1598
+ if (!fs10.existsSync(dir)) return results;
1599
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
1600
+ for (const entry of entries) {
1601
+ const fullPath = path9.join(dir, entry.name);
1602
+ if (entry.isDirectory()) {
1603
+ results.push(...findBffFiles(fullPath));
1604
+ } else if (entry.isFile() && entry.name.endsWith(".dart") && !entry.name.includes(".g.")) {
1605
+ results.push(fullPath);
1606
+ }
1607
+ }
1608
+ return results.sort();
1609
+ }
1610
+ function inferBffFile(bffFiles, endpointPath) {
1611
+ const firstSegment = endpointPath.replace(/^\//, "").split("/")[0].toLowerCase();
1612
+ if (!firstSegment) return null;
1613
+ const withDomains = bffFiles.map((f) => {
1614
+ const base = path9.basename(f, ".dart");
1615
+ const domain = base.replace(/^bff_/, "").replace(/_api$/, "");
1616
+ return { file: f, domain };
1617
+ });
1618
+ const exact = withDomains.filter((x) => x.domain === firstSegment);
1619
+ if (exact.length === 1) return exact[0].file;
1620
+ const singular = firstSegment.replace(/s$/, "");
1621
+ const singularMatch = withDomains.filter((x) => x.domain === singular);
1622
+ if (singularMatch.length === 1) return singularMatch[0].file;
1623
+ const endsWith = withDomains.filter(
1624
+ (x) => x.domain.endsWith(firstSegment) || x.domain.endsWith(singular)
1625
+ );
1626
+ if (endsWith.length === 1) return endsWith[0].file;
1627
+ const contains = withDomains.filter(
1628
+ (x) => firstSegment.includes(x.domain) || singular.includes(x.domain)
1629
+ );
1630
+ if (contains.length === 1) return contains[0].file;
1631
+ return null;
1632
+ }
1633
+ async function resolveEndpointProject() {
1634
+ const s = clack.spinner();
1635
+ s.start("Finding BFF package...");
1636
+ const monorepoRoot = findMonorepoRoot(process.cwd());
1637
+ if (monorepoRoot) {
1638
+ const packageBases = ["packages", "packages/features"];
1639
+ for (const base of packageBases) {
1640
+ const baseDir = path9.join(monorepoRoot, base);
1641
+ if (!fs10.existsSync(baseDir)) continue;
1642
+ const entries = fs10.readdirSync(baseDir, { withFileTypes: true });
1643
+ for (const entry of entries) {
1644
+ if (!entry.isDirectory()) continue;
1645
+ const candidate = path9.join(baseDir, entry.name);
1646
+ if (!fs10.existsSync(path9.join(candidate, "pubspec.yaml"))) continue;
1647
+ if (fs10.existsSync(path9.join(candidate, "lib", "data", "api", "bff"))) {
1648
+ const project = analyzeProject(candidate);
1649
+ if (project) {
1650
+ s.stop(`Using ${chalk5.green(project.projectName)}`);
1651
+ return project;
1652
+ }
1653
+ }
1654
+ }
1655
+ }
1656
+ }
1657
+ const cwdProject = analyzeProject(process.cwd());
1658
+ if (cwdProject && fs10.existsSync(path9.join(cwdProject.projectRoot, "lib", "data", "api", "bff"))) {
1659
+ s.stop(`Using ${chalk5.green(cwdProject.projectName)}`);
1660
+ return cwdProject;
1661
+ }
1662
+ s.stop("No BFF package found");
1663
+ clack.outro(chalk5.red("Could not find a package with lib/data/api/bff/."));
1664
+ return null;
1665
+ }
1666
+ async function endpointFlow() {
1667
+ const project = await resolveEndpointProject();
1668
+ if (!project) return;
1591
1669
  const lib = path9.join(project.projectRoot, "lib");
1670
+ const bffDir = path9.join(lib, "data", "api", "bff");
1671
+ const bffFiles = findBffFiles(bffDir);
1672
+ if (bffFiles.length === 0) {
1673
+ clack.outro(chalk5.red("No BFF API files found. Ensure lib/data/api/bff/ exists with .dart files."));
1674
+ return;
1675
+ }
1592
1676
  const httpMethod = await clack.select({
1593
1677
  message: "HTTP Method",
1594
1678
  options: [
@@ -1614,50 +1698,22 @@ async function endpointFlow(project) {
1614
1698
  clack.cancel("Cancelled");
1615
1699
  return;
1616
1700
  }
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
- }
1701
+ let bffApiFile = inferBffFile(bffFiles, endpointPath);
1702
+ if (bffApiFile) {
1703
+ clack.log.info(`Auto-detected BFF file: ${chalk5.cyan(path9.basename(bffApiFile))}`);
1648
1704
  } 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
- }
1705
+ const selected = await clack.select({
1706
+ message: "Could not auto-detect BFF file. Select one:",
1707
+ options: bffFiles.map((f) => ({
1708
+ value: f,
1709
+ label: path9.relative(bffDir, f)
1710
+ }))
1655
1711
  });
1656
- if (clack.isCancel(customPath)) {
1712
+ if (clack.isCancel(selected)) {
1657
1713
  clack.cancel("Cancelled");
1658
1714
  return;
1659
1715
  }
1660
- bffApiFile = path9.resolve(customPath);
1716
+ bffApiFile = selected;
1661
1717
  }
1662
1718
  const methodLower = httpMethod.toLowerCase();
1663
1719
  const pathSegments = endpointPath.replace(/^\//, "").split("/").filter((s) => !s.startsWith("{"));
@@ -1680,6 +1736,7 @@ async function endpointFlow(project) {
1680
1736
  try {
1681
1737
  await createEndpoint({
1682
1738
  projectRoot: project.projectRoot,
1739
+ projectName: project.projectName,
1683
1740
  httpMethod,
1684
1741
  endpointPath,
1685
1742
  bffApiFile,
@@ -1732,12 +1789,9 @@ async function interactiveMode() {
1732
1789
  await useCaseFlow(project);
1733
1790
  break;
1734
1791
  }
1735
- case "endpoint": {
1736
- const project = await resolveProject();
1737
- if (!project) return;
1738
- await endpointFlow(project);
1792
+ case "endpoint":
1793
+ await endpointFlow();
1739
1794
  break;
1740
- }
1741
1795
  }
1742
1796
  }
1743
1797
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wlmaker",
3
- "version": "1.2.10",
3
+ "version": "1.2.12",
4
4
  "description": "Create Flutter BLoCs with Freezed sealed classes from the terminal",
5
5
  "keywords": [
6
6
  "flutter",