wlmaker 1.2.9 → 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.
- package/dist/cli.mjs +70 -405
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -968,97 +968,16 @@ import * as path8 from "path";
|
|
|
968
968
|
import chalk4 from "chalk";
|
|
969
969
|
import { pascalCase as pascalCase4, camelCase } from "change-case";
|
|
970
970
|
|
|
971
|
-
// src/core/json-to-dart.ts
|
|
972
|
-
function jsonToFields(jsonStr) {
|
|
973
|
-
const parsed = JSON.parse(jsonStr);
|
|
974
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
975
|
-
throw new Error("Expected a JSON object at the top level");
|
|
976
|
-
}
|
|
977
|
-
return objectToFields(parsed);
|
|
978
|
-
}
|
|
979
|
-
function objectToFields(obj) {
|
|
980
|
-
const fields = [];
|
|
981
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
982
|
-
fields.push({
|
|
983
|
-
name: key,
|
|
984
|
-
dartType: dartTypeOf(value, key),
|
|
985
|
-
isNullable: value === null
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
return fields;
|
|
989
|
-
}
|
|
990
|
-
function dartTypeOf(value, nameHint) {
|
|
991
|
-
if (value === null) return "dynamic";
|
|
992
|
-
if (typeof value === "string") return "String";
|
|
993
|
-
if (typeof value === "boolean") return "bool";
|
|
994
|
-
if (typeof value === "number") {
|
|
995
|
-
return Number.isInteger(value) ? "int" : "double";
|
|
996
|
-
}
|
|
997
|
-
if (Array.isArray(value)) {
|
|
998
|
-
if (value.length === 0) return "List<dynamic>";
|
|
999
|
-
const elementType = dartTypeOf(value[0], nameHint);
|
|
1000
|
-
return `List<${elementType}>`;
|
|
1001
|
-
}
|
|
1002
|
-
if (typeof value === "object") {
|
|
1003
|
-
return "Map<String, dynamic>";
|
|
1004
|
-
}
|
|
1005
|
-
return "dynamic";
|
|
1006
|
-
}
|
|
1007
|
-
function fieldsToConstructorParams(fields, indent = " ") {
|
|
1008
|
-
return fields.map((f) => {
|
|
1009
|
-
const nullable = f.isNullable ? "?" : "";
|
|
1010
|
-
return `${indent}required ${f.dartType}${nullable} ${f.name},`;
|
|
1011
|
-
}).join("\n");
|
|
1012
|
-
}
|
|
1013
|
-
function fieldsToFromJson(fields, modelName) {
|
|
1014
|
-
const entries = fields.map((f) => {
|
|
1015
|
-
if (f.dartType.startsWith("List<")) {
|
|
1016
|
-
return ` ${f.name}: (${_jsonFieldName(f)} as List<dynamic>).map((e) => e as ${_extractGenericType(f.dartType)}).toList(),`;
|
|
1017
|
-
}
|
|
1018
|
-
return ` ${f.name}: ${_jsonFieldName(f)} as ${f.dartType}${f.isNullable ? "?" : ""},`;
|
|
1019
|
-
}).join("\n");
|
|
1020
|
-
return `factory ${modelName}.fromJson(Map<String, dynamic> json) => ${modelName}(
|
|
1021
|
-
${entries}
|
|
1022
|
-
);`;
|
|
1023
|
-
}
|
|
1024
|
-
function fieldsToJson(fields, modelName) {
|
|
1025
|
-
const entries = fields.map((f) => ` '${f.name}': ${f.name},`).join("\n");
|
|
1026
|
-
return `Map<String, dynamic> toJson() => {
|
|
1027
|
-
${entries}
|
|
1028
|
-
};`;
|
|
1029
|
-
}
|
|
1030
|
-
function _jsonFieldName(f) {
|
|
1031
|
-
return `json['${f.name}']`;
|
|
1032
|
-
}
|
|
1033
|
-
function _extractGenericType(listType) {
|
|
1034
|
-
const match = listType.match(/^List<(.+)>$/);
|
|
1035
|
-
return match ? match[1] : "dynamic";
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
971
|
// src/core/endpoint-templates.ts
|
|
1039
|
-
function
|
|
1040
|
-
const params = fieldsToConstructorParams(fields);
|
|
1041
|
-
const props = fields.map((f) => {
|
|
1042
|
-
const nullable = f.isNullable ? "?" : "";
|
|
1043
|
-
return ` final ${f.dartType}${nullable} ${f.name};`;
|
|
1044
|
-
}).join("\n");
|
|
972
|
+
function entityBoilerplate(pascal) {
|
|
1045
973
|
return `class ${pascal}Entity {
|
|
1046
|
-
|
|
974
|
+
// TODO: Define fields
|
|
1047
975
|
|
|
1048
|
-
const ${pascal}Entity(
|
|
1049
|
-
${params}
|
|
1050
|
-
});
|
|
976
|
+
const ${pascal}Entity();
|
|
1051
977
|
}
|
|
1052
978
|
`;
|
|
1053
979
|
}
|
|
1054
|
-
function
|
|
1055
|
-
const params = fieldsToConstructorParams(fields);
|
|
1056
|
-
const props = fields.map((f) => {
|
|
1057
|
-
const nullable = f.isNullable ? "?" : "";
|
|
1058
|
-
return ` final ${f.dartType}${nullable} ${f.name};`;
|
|
1059
|
-
}).join("\n");
|
|
1060
|
-
const fromJson = fieldsToFromJson(fields, `${pascal}Model`);
|
|
1061
|
-
const toJson = fieldsToJson(fields, `${pascal}Model`);
|
|
980
|
+
function modelBoilerplate(name, pascal) {
|
|
1062
981
|
return `import 'package:json_annotation/json_annotation.dart';
|
|
1063
982
|
import '../../domain/entities/${name}/${name}_entity.dart';
|
|
1064
983
|
|
|
@@ -1066,15 +985,7 @@ part '${name}_model.g.dart';
|
|
|
1066
985
|
|
|
1067
986
|
@JsonSerializable()
|
|
1068
987
|
class ${pascal}Model extends ${pascal}Entity {
|
|
1069
|
-
const ${pascal}Model(
|
|
1070
|
-
${params}
|
|
1071
|
-
}) : super(
|
|
1072
|
-
${fields.map((f) => ` ${f.name}: ${f.name},`).join("\n")}
|
|
1073
|
-
);
|
|
1074
|
-
|
|
1075
|
-
${fromJson}
|
|
1076
|
-
|
|
1077
|
-
${toJson}
|
|
988
|
+
const ${pascal}Model() : super();
|
|
1078
989
|
|
|
1079
990
|
factory ${pascal}Model.fromJson(Map<String, dynamic> json) =>
|
|
1080
991
|
_$${pascal}ModelFromJson(json);
|
|
@@ -1084,23 +995,16 @@ ${fields.map((f) => ` ${f.name}: ${f.name},`).join("\n")}
|
|
|
1084
995
|
}
|
|
1085
996
|
`;
|
|
1086
997
|
}
|
|
1087
|
-
function
|
|
1088
|
-
const params = fieldsToConstructorParams(fields);
|
|
1089
|
-
const props = fields.map((f) => {
|
|
1090
|
-
const nullable = f.isNullable ? "?" : "";
|
|
1091
|
-
return ` final ${f.dartType}${nullable} ${f.name};`;
|
|
1092
|
-
}).join("\n");
|
|
998
|
+
function requestModelBoilerplate(name, pascal) {
|
|
1093
999
|
return `import 'package:json_annotation/json_annotation.dart';
|
|
1094
1000
|
|
|
1095
1001
|
part '${name}_request_model.g.dart';
|
|
1096
1002
|
|
|
1097
1003
|
@JsonSerializable()
|
|
1098
1004
|
class ${pascal}RequestModel {
|
|
1099
|
-
|
|
1005
|
+
// TODO: Define fields
|
|
1100
1006
|
|
|
1101
|
-
${pascal}RequestModel(
|
|
1102
|
-
${params}
|
|
1103
|
-
});
|
|
1007
|
+
${pascal}RequestModel();
|
|
1104
1008
|
|
|
1105
1009
|
factory ${pascal}RequestModel.fromJson(Map<String, dynamic> json) =>
|
|
1106
1010
|
_$${pascal}RequestModelFromJson(json);
|
|
@@ -1234,46 +1138,51 @@ function injectExport(filePath, exportLine) {
|
|
|
1234
1138
|
// src/core/create-endpoint.ts
|
|
1235
1139
|
async function createEndpoint(options) {
|
|
1236
1140
|
const lib = path8.join(options.projectRoot, "lib");
|
|
1237
|
-
const pascal = pascalCase4(options.
|
|
1141
|
+
const pascal = pascalCase4(options.useCaseName);
|
|
1238
1142
|
const useCasePascal = pascalCase4(options.useCaseName);
|
|
1239
1143
|
const useCaseSnake = options.useCaseName;
|
|
1240
|
-
const
|
|
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;
|
|
1241
1153
|
const pathParams = extractPathParams(options.endpointPath);
|
|
1242
|
-
const methodParams = buildMethodParams(options, pathParams,
|
|
1154
|
+
const methodParams = buildMethodParams(options, pathParams, needsBody);
|
|
1243
1155
|
const methodParamsForSignature = methodParams.map((p) => ({ name: p.name, type: p.type }));
|
|
1244
1156
|
const returnType = `${pascal}Entity`;
|
|
1245
|
-
const repositoryInterfaceSnake = path8.basename(
|
|
1157
|
+
const repositoryInterfaceSnake = path8.basename(repositoryInterfaceFile, ".dart");
|
|
1246
1158
|
const spinner2 = (msg) => console.log(chalk4.cyan(` \u2192 ${msg}`));
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
if (hasRequestBody && options.requestFields && options.requestModelName) {
|
|
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) {
|
|
1264
1174
|
spinner2("Generating request model");
|
|
1265
|
-
const
|
|
1266
|
-
const reqModelDir = path8.join(lib, "data", "models", options.feature);
|
|
1175
|
+
const reqModelDir = path8.join(lib, "data", "models", feature, useCaseSnake);
|
|
1267
1176
|
fs9.mkdirSync(reqModelDir, { recursive: true });
|
|
1268
1177
|
fs9.writeFileSync(
|
|
1269
|
-
path8.join(reqModelDir, `${
|
|
1270
|
-
|
|
1178
|
+
path8.join(reqModelDir, `${useCaseSnake}_request_model.dart`),
|
|
1179
|
+
requestModelBoilerplate(useCaseSnake, pascal)
|
|
1271
1180
|
);
|
|
1272
1181
|
}
|
|
1273
1182
|
spinner2("Injecting Retrofit method");
|
|
1274
1183
|
const bffPath = path8.resolve(options.bffApiFile);
|
|
1275
1184
|
if (fs9.existsSync(bffPath)) {
|
|
1276
|
-
const retrofitParams = buildRetrofitParams(options, pathParams,
|
|
1185
|
+
const retrofitParams = buildRetrofitParams(options, pathParams, needsBody);
|
|
1277
1186
|
const retrofitReturnType = `${pascal}Model`;
|
|
1278
1187
|
const method = retrofitMethod(
|
|
1279
1188
|
camelCase(options.useCaseName),
|
|
@@ -1283,53 +1192,53 @@ async function createEndpoint(options) {
|
|
|
1283
1192
|
retrofitReturnType
|
|
1284
1193
|
);
|
|
1285
1194
|
injectMethod(bffPath, extractClassName(bffPath), method);
|
|
1286
|
-
const modelImport = `import 'package:${
|
|
1195
|
+
const modelImport = `import 'package:${feature}/data/models/${useCaseSnake}/${useCaseSnake}_model.dart';`;
|
|
1287
1196
|
injectImport(bffPath, modelImport);
|
|
1288
1197
|
} else {
|
|
1289
1198
|
console.log(chalk4.yellow(` \u26A0 BFF API file not found: ${bffPath}`));
|
|
1290
1199
|
}
|
|
1291
1200
|
spinner2("Injecting datasource method");
|
|
1292
|
-
const dsPath = path8.resolve(
|
|
1201
|
+
const dsPath = path8.resolve(datasourceFile);
|
|
1293
1202
|
if (fs9.existsSync(dsPath)) {
|
|
1294
1203
|
const dsMethod = datasourceMethod(
|
|
1295
1204
|
camelCase(options.useCaseName),
|
|
1296
1205
|
returnType,
|
|
1297
1206
|
methodParamsForSignature
|
|
1298
1207
|
);
|
|
1299
|
-
injectMethod(dsPath,
|
|
1208
|
+
injectMethod(dsPath, datasourceClassName, dsMethod);
|
|
1300
1209
|
} else {
|
|
1301
1210
|
console.log(chalk4.yellow(` \u26A0 Datasource file not found: ${dsPath}`));
|
|
1302
1211
|
}
|
|
1303
1212
|
spinner2("Injecting repository interface method");
|
|
1304
|
-
const repoIfacePath = path8.resolve(
|
|
1213
|
+
const repoIfacePath = path8.resolve(repositoryInterfaceFile);
|
|
1305
1214
|
if (fs9.existsSync(repoIfacePath)) {
|
|
1306
1215
|
const ifaceMethod = repositoryInterfaceMethod(
|
|
1307
1216
|
camelCase(options.useCaseName),
|
|
1308
1217
|
returnType,
|
|
1309
1218
|
methodParamsForSignature
|
|
1310
1219
|
);
|
|
1311
|
-
injectMethod(repoIfacePath,
|
|
1312
|
-
const entityImport = `import 'package:${
|
|
1220
|
+
injectMethod(repoIfacePath, repositoryInterfaceName, ifaceMethod);
|
|
1221
|
+
const entityImport = `import 'package:${feature}/domain/entities/${useCaseSnake}/${useCaseSnake}_entity.dart';`;
|
|
1313
1222
|
injectImport(repoIfacePath, entityImport);
|
|
1314
1223
|
} else {
|
|
1315
1224
|
console.log(chalk4.yellow(` \u26A0 Repository interface not found: ${repoIfacePath}`));
|
|
1316
1225
|
}
|
|
1317
|
-
spinner2("
|
|
1318
|
-
const repoImplPath = path8.resolve(
|
|
1226
|
+
spinner2("Injecting repository implementation method");
|
|
1227
|
+
const repoImplPath = path8.resolve(repositoryImplFile);
|
|
1319
1228
|
if (fs9.existsSync(repoImplPath)) {
|
|
1320
|
-
const dsVarName = camelCase(
|
|
1229
|
+
const dsVarName = camelCase(datasourceClassName);
|
|
1321
1230
|
const implMethod = repositoryImplMethod(
|
|
1322
1231
|
camelCase(options.useCaseName),
|
|
1323
1232
|
returnType,
|
|
1324
1233
|
methodParamsForSignature,
|
|
1325
1234
|
dsVarName
|
|
1326
1235
|
);
|
|
1327
|
-
injectMethod(repoImplPath,
|
|
1236
|
+
injectMethod(repoImplPath, repositoryImplClassName, implMethod);
|
|
1328
1237
|
} else {
|
|
1329
1238
|
console.log(chalk4.yellow(` \u26A0 Repository implementation not found: ${repoImplPath}`));
|
|
1330
1239
|
}
|
|
1331
1240
|
spinner2("Generating UseCase");
|
|
1332
|
-
const useCaseDir = path8.join(lib, "domain", "usecases",
|
|
1241
|
+
const useCaseDir = path8.join(lib, "domain", "usecases", feature);
|
|
1333
1242
|
fs9.mkdirSync(useCaseDir, { recursive: true });
|
|
1334
1243
|
fs9.writeFileSync(
|
|
1335
1244
|
path8.join(useCaseDir, `${useCaseSnake}_usecase.dart`),
|
|
@@ -1339,26 +1248,22 @@ async function createEndpoint(options) {
|
|
|
1339
1248
|
camelCase(options.useCaseName),
|
|
1340
1249
|
methodParamsForSignature,
|
|
1341
1250
|
returnType,
|
|
1342
|
-
|
|
1251
|
+
repositoryInterfaceSnake
|
|
1343
1252
|
)
|
|
1344
1253
|
);
|
|
1345
1254
|
spinner2("Updating barrel file");
|
|
1346
1255
|
const barrelPath = path8.join(useCaseDir, "usecases.dart");
|
|
1347
1256
|
const exportLine = `export '${useCaseSnake}_usecase.dart';`;
|
|
1348
1257
|
injectExport(barrelPath, exportLine);
|
|
1349
|
-
if (options.runBuildRunner) {
|
|
1350
|
-
const projectRoot = findPubspecDir(options.projectRoot);
|
|
1351
|
-
if (projectRoot && hasBuildRunner(projectRoot)) {
|
|
1352
|
-
spinner2("Running build_runner...");
|
|
1353
|
-
await runBuildRunner(projectRoot);
|
|
1354
|
-
console.log(chalk4.green(" \u2713 build_runner completed."));
|
|
1355
|
-
} else {
|
|
1356
|
-
console.log(chalk4.yellow(" \u26A0 Skipping build_runner (not found)."));
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
1258
|
console.log(chalk4.green(`
|
|
1360
1259
|
\u2713 Endpoint "${options.useCaseName}" generated successfully.`));
|
|
1361
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
|
+
}
|
|
1362
1267
|
function extractPathParams(endpointPath) {
|
|
1363
1268
|
const regex = /\{(\w+)\}/g;
|
|
1364
1269
|
const params = [];
|
|
@@ -1370,8 +1275,8 @@ function extractPathParams(endpointPath) {
|
|
|
1370
1275
|
}
|
|
1371
1276
|
function buildMethodParams(options, pathParams, hasRequestBody) {
|
|
1372
1277
|
const params = [...pathParams];
|
|
1373
|
-
if (hasRequestBody
|
|
1374
|
-
const reqPascal = pascalCase4(options.
|
|
1278
|
+
if (hasRequestBody) {
|
|
1279
|
+
const reqPascal = pascalCase4(options.useCaseName);
|
|
1375
1280
|
params.push({ name: "body", type: `${reqPascal}RequestModel` });
|
|
1376
1281
|
}
|
|
1377
1282
|
return params;
|
|
@@ -1381,8 +1286,8 @@ function buildRetrofitParams(options, pathParams, hasRequestBody) {
|
|
|
1381
1286
|
for (const pp of pathParams) {
|
|
1382
1287
|
params.push({ ...pp, isPath: true });
|
|
1383
1288
|
}
|
|
1384
|
-
if (hasRequestBody
|
|
1385
|
-
const reqPascal = pascalCase4(options.
|
|
1289
|
+
if (hasRequestBody) {
|
|
1290
|
+
const reqPascal = pascalCase4(options.useCaseName);
|
|
1386
1291
|
params.push({ name: "body", type: `${reqPascal}RequestModel`, isBody: true });
|
|
1387
1292
|
}
|
|
1388
1293
|
return params;
|
|
@@ -1395,7 +1300,6 @@ function extractClassName(filePath) {
|
|
|
1395
1300
|
}
|
|
1396
1301
|
|
|
1397
1302
|
// src/interactive.ts
|
|
1398
|
-
import { pascalCase as pascalCase5 } from "change-case";
|
|
1399
1303
|
var SNAKE_CASE_REGEX4 = /^[a-z][a-z0-9_]*$/;
|
|
1400
1304
|
async function resolveProject() {
|
|
1401
1305
|
const s = clack.spinner();
|
|
@@ -1700,7 +1604,7 @@ async function endpointFlow(project) {
|
|
|
1700
1604
|
return;
|
|
1701
1605
|
}
|
|
1702
1606
|
const endpointPath = await clack.text({
|
|
1703
|
-
message: "Endpoint path (e.g. /
|
|
1607
|
+
message: "Endpoint path (e.g. /products/{id})",
|
|
1704
1608
|
placeholder: "/api/users/{id}",
|
|
1705
1609
|
validate: (v) => {
|
|
1706
1610
|
if (!v.trim()) return "Path is required";
|
|
@@ -1717,34 +1621,16 @@ async function endpointFlow(project) {
|
|
|
1717
1621
|
if (dartFiles.length > 0) {
|
|
1718
1622
|
const selected = await clack.select({
|
|
1719
1623
|
message: "Select BFF API file",
|
|
1720
|
-
options:
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
})),
|
|
1725
|
-
{ value: "__custom__", label: "Custom path..." }
|
|
1726
|
-
]
|
|
1624
|
+
options: dartFiles.map((f) => ({
|
|
1625
|
+
value: path9.join(bffDir, f),
|
|
1626
|
+
label: f
|
|
1627
|
+
}))
|
|
1727
1628
|
});
|
|
1728
1629
|
if (clack.isCancel(selected)) {
|
|
1729
1630
|
clack.cancel("Cancelled");
|
|
1730
1631
|
return;
|
|
1731
1632
|
}
|
|
1732
|
-
|
|
1733
|
-
const customPath = await clack.text({
|
|
1734
|
-
message: "BFF API file path",
|
|
1735
|
-
placeholder: "lib/data/api/bff/my_api.dart",
|
|
1736
|
-
validate: (v) => {
|
|
1737
|
-
if (!v.trim()) return "Path is required";
|
|
1738
|
-
}
|
|
1739
|
-
});
|
|
1740
|
-
if (clack.isCancel(customPath)) {
|
|
1741
|
-
clack.cancel("Cancelled");
|
|
1742
|
-
return;
|
|
1743
|
-
}
|
|
1744
|
-
bffApiFile = path9.resolve(customPath);
|
|
1745
|
-
} else {
|
|
1746
|
-
bffApiFile = selected;
|
|
1747
|
-
}
|
|
1633
|
+
bffApiFile = selected;
|
|
1748
1634
|
} else {
|
|
1749
1635
|
const customPath = await clack.text({
|
|
1750
1636
|
message: "BFF API file path (no .dart files found in bff/)",
|
|
@@ -1773,128 +1659,9 @@ async function endpointFlow(project) {
|
|
|
1773
1659
|
}
|
|
1774
1660
|
bffApiFile = path9.resolve(customPath);
|
|
1775
1661
|
}
|
|
1776
|
-
const
|
|
1777
|
-
message: "Response model",
|
|
1778
|
-
options: [
|
|
1779
|
-
{ value: "existing", label: "Use existing model", hint: "Reference an already created model" },
|
|
1780
|
-
{ value: "json", label: "Paste JSON", hint: "Generate entity + model from JSON" }
|
|
1781
|
-
]
|
|
1782
|
-
});
|
|
1783
|
-
if (clack.isCancel(responseChoice)) {
|
|
1784
|
-
clack.cancel("Cancelled");
|
|
1785
|
-
return;
|
|
1786
|
-
}
|
|
1787
|
-
let responseModelName = "";
|
|
1788
|
-
let responseFields;
|
|
1789
|
-
if (responseChoice === "json") {
|
|
1790
|
-
const modelName = await clack.text({
|
|
1791
|
-
message: "Response model name (snake_case)",
|
|
1792
|
-
placeholder: "e.g. user_profile",
|
|
1793
|
-
validate: (v) => {
|
|
1794
|
-
if (!v.trim()) return "Name is required";
|
|
1795
|
-
if (!SNAKE_CASE_REGEX4.test(v)) return "Must be snake_case";
|
|
1796
|
-
}
|
|
1797
|
-
});
|
|
1798
|
-
if (clack.isCancel(modelName)) {
|
|
1799
|
-
clack.cancel("Cancelled");
|
|
1800
|
-
return;
|
|
1801
|
-
}
|
|
1802
|
-
responseModelName = modelName;
|
|
1803
|
-
const jsonStr = await clack.text({
|
|
1804
|
-
message: "Paste the JSON response",
|
|
1805
|
-
placeholder: '{ "id": 1, "name": "John" }',
|
|
1806
|
-
validate: (v) => {
|
|
1807
|
-
if (!v.trim()) return "JSON is required";
|
|
1808
|
-
try {
|
|
1809
|
-
JSON.parse(v);
|
|
1810
|
-
} catch {
|
|
1811
|
-
return "Invalid JSON";
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
1815
|
-
if (clack.isCancel(jsonStr)) {
|
|
1816
|
-
clack.cancel("Cancelled");
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
responseFields = jsonToFields(jsonStr);
|
|
1820
|
-
} else {
|
|
1821
|
-
const modelName = await clack.text({
|
|
1822
|
-
message: "Existing response model name (snake_case)",
|
|
1823
|
-
placeholder: "e.g. user_profile",
|
|
1824
|
-
validate: (v) => {
|
|
1825
|
-
if (!v.trim()) return "Name is required";
|
|
1826
|
-
}
|
|
1827
|
-
});
|
|
1828
|
-
if (clack.isCancel(modelName)) {
|
|
1829
|
-
clack.cancel("Cancelled");
|
|
1830
|
-
return;
|
|
1831
|
-
}
|
|
1832
|
-
responseModelName = modelName;
|
|
1833
|
-
}
|
|
1834
|
-
let requestModelName;
|
|
1835
|
-
let requestFields;
|
|
1836
|
-
const needsBody = ["POST", "PUT", "PATCH"].includes(httpMethod);
|
|
1837
|
-
if (needsBody) {
|
|
1838
|
-
const requestChoice = await clack.select({
|
|
1839
|
-
message: "Request body",
|
|
1840
|
-
options: [
|
|
1841
|
-
{ value: "json", label: "Paste JSON", hint: "Generate request model from JSON" },
|
|
1842
|
-
{ value: "existing", label: "Use existing model", hint: "Reference an already created model" },
|
|
1843
|
-
{ value: "none", label: "No body", hint: "Just path/query params" }
|
|
1844
|
-
]
|
|
1845
|
-
});
|
|
1846
|
-
if (clack.isCancel(requestChoice)) {
|
|
1847
|
-
clack.cancel("Cancelled");
|
|
1848
|
-
return;
|
|
1849
|
-
}
|
|
1850
|
-
if (requestChoice === "json") {
|
|
1851
|
-
const reqName = await clack.text({
|
|
1852
|
-
message: "Request model name (snake_case)",
|
|
1853
|
-
placeholder: `e.g. create_${responseModelName}`,
|
|
1854
|
-
validate: (v) => {
|
|
1855
|
-
if (!v.trim()) return "Name is required";
|
|
1856
|
-
if (!SNAKE_CASE_REGEX4.test(v)) return "Must be snake_case";
|
|
1857
|
-
}
|
|
1858
|
-
});
|
|
1859
|
-
if (clack.isCancel(reqName)) {
|
|
1860
|
-
clack.cancel("Cancelled");
|
|
1861
|
-
return;
|
|
1862
|
-
}
|
|
1863
|
-
requestModelName = reqName;
|
|
1864
|
-
const reqJson = await clack.text({
|
|
1865
|
-
message: "Paste the request JSON body",
|
|
1866
|
-
placeholder: '{ "name": "John", "email": "john@example.com" }',
|
|
1867
|
-
validate: (v) => {
|
|
1868
|
-
if (!v.trim()) return "JSON is required";
|
|
1869
|
-
try {
|
|
1870
|
-
JSON.parse(v);
|
|
1871
|
-
} catch {
|
|
1872
|
-
return "Invalid JSON";
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
});
|
|
1876
|
-
if (clack.isCancel(reqJson)) {
|
|
1877
|
-
clack.cancel("Cancelled");
|
|
1878
|
-
return;
|
|
1879
|
-
}
|
|
1880
|
-
requestFields = jsonToFields(reqJson);
|
|
1881
|
-
} else if (requestChoice === "existing") {
|
|
1882
|
-
const reqName = await clack.text({
|
|
1883
|
-
message: "Existing request model name (snake_case)",
|
|
1884
|
-
placeholder: `e.g. create_${responseModelName}`,
|
|
1885
|
-
validate: (v) => {
|
|
1886
|
-
if (!v.trim()) return "Name is required";
|
|
1887
|
-
}
|
|
1888
|
-
});
|
|
1889
|
-
if (clack.isCancel(reqName)) {
|
|
1890
|
-
clack.cancel("Cancelled");
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
requestModelName = reqName;
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1662
|
+
const methodLower = httpMethod.toLowerCase();
|
|
1896
1663
|
const pathSegments = endpointPath.replace(/^\//, "").split("/").filter((s) => !s.startsWith("{"));
|
|
1897
|
-
const inferredName = pathSegments.length > 0 ?
|
|
1664
|
+
const inferredName = pathSegments.length > 0 ? `${methodLower}_${pathSegments.join("_")}` : `${methodLower}_data`;
|
|
1898
1665
|
const useCaseName = await clack.text({
|
|
1899
1666
|
message: "UseCase name (snake_case)",
|
|
1900
1667
|
placeholder: inferredName,
|
|
@@ -1908,96 +1675,6 @@ async function endpointFlow(project) {
|
|
|
1908
1675
|
clack.cancel("Cancelled");
|
|
1909
1676
|
return;
|
|
1910
1677
|
}
|
|
1911
|
-
const datasourceFile = await clack.text({
|
|
1912
|
-
message: "Datasource file path",
|
|
1913
|
-
placeholder: `lib/data/datasources/${responseModelName}_datasource.dart`,
|
|
1914
|
-
validate: (v) => {
|
|
1915
|
-
if (!v.trim()) return "Path is required";
|
|
1916
|
-
}
|
|
1917
|
-
});
|
|
1918
|
-
if (clack.isCancel(datasourceFile)) {
|
|
1919
|
-
clack.cancel("Cancelled");
|
|
1920
|
-
return;
|
|
1921
|
-
}
|
|
1922
|
-
const datasourceClassName = await clack.text({
|
|
1923
|
-
message: "Datasource class name",
|
|
1924
|
-
placeholder: `${pascalCase5(responseModelName)}Datasource`,
|
|
1925
|
-
initialValue: `${pascalCase5(responseModelName)}Datasource`,
|
|
1926
|
-
validate: (v) => {
|
|
1927
|
-
if (!v.trim()) return "Class name is required";
|
|
1928
|
-
}
|
|
1929
|
-
});
|
|
1930
|
-
if (clack.isCancel(datasourceClassName)) {
|
|
1931
|
-
clack.cancel("Cancelled");
|
|
1932
|
-
return;
|
|
1933
|
-
}
|
|
1934
|
-
const repositoryInterfaceFile = await clack.text({
|
|
1935
|
-
message: "Repository interface file path",
|
|
1936
|
-
placeholder: `lib/domain/repositories/${responseModelName}_repository.dart`,
|
|
1937
|
-
validate: (v) => {
|
|
1938
|
-
if (!v.trim()) return "Path is required";
|
|
1939
|
-
}
|
|
1940
|
-
});
|
|
1941
|
-
if (clack.isCancel(repositoryInterfaceFile)) {
|
|
1942
|
-
clack.cancel("Cancelled");
|
|
1943
|
-
return;
|
|
1944
|
-
}
|
|
1945
|
-
const repositoryInterfaceName2 = await clack.text({
|
|
1946
|
-
message: "Repository interface name",
|
|
1947
|
-
placeholder: `${pascalCase5(responseModelName)}Repository`,
|
|
1948
|
-
initialValue: `${pascalCase5(responseModelName)}Repository`,
|
|
1949
|
-
validate: (v) => {
|
|
1950
|
-
if (!v.trim()) return "Name is required";
|
|
1951
|
-
}
|
|
1952
|
-
});
|
|
1953
|
-
if (clack.isCancel(repositoryInterfaceName2)) {
|
|
1954
|
-
clack.cancel("Cancelled");
|
|
1955
|
-
return;
|
|
1956
|
-
}
|
|
1957
|
-
const repositoryImplFile = await clack.text({
|
|
1958
|
-
message: "Repository implementation file path",
|
|
1959
|
-
placeholder: `lib/data/repositories/${responseModelName}_repository_impl.dart`,
|
|
1960
|
-
validate: (v) => {
|
|
1961
|
-
if (!v.trim()) return "Path is required";
|
|
1962
|
-
}
|
|
1963
|
-
});
|
|
1964
|
-
if (clack.isCancel(repositoryImplFile)) {
|
|
1965
|
-
clack.cancel("Cancelled");
|
|
1966
|
-
return;
|
|
1967
|
-
}
|
|
1968
|
-
const repositoryImplClassName = await clack.text({
|
|
1969
|
-
message: "Repository implementation class name",
|
|
1970
|
-
placeholder: `${pascalCase5(responseModelName)}RepositoryImpl`,
|
|
1971
|
-
initialValue: `${pascalCase5(responseModelName)}RepositoryImpl`,
|
|
1972
|
-
validate: (v) => {
|
|
1973
|
-
if (!v.trim()) return "Name is required";
|
|
1974
|
-
}
|
|
1975
|
-
});
|
|
1976
|
-
if (clack.isCancel(repositoryImplClassName)) {
|
|
1977
|
-
clack.cancel("Cancelled");
|
|
1978
|
-
return;
|
|
1979
|
-
}
|
|
1980
|
-
const feature = await clack.text({
|
|
1981
|
-
message: "Feature name (snake_case, for directory grouping)",
|
|
1982
|
-
placeholder: responseModelName,
|
|
1983
|
-
initialValue: responseModelName,
|
|
1984
|
-
validate: (v) => {
|
|
1985
|
-
if (!v.trim()) return "Feature name is required";
|
|
1986
|
-
if (!SNAKE_CASE_REGEX4.test(v)) return "Must be snake_case";
|
|
1987
|
-
}
|
|
1988
|
-
});
|
|
1989
|
-
if (clack.isCancel(feature)) {
|
|
1990
|
-
clack.cancel("Cancelled");
|
|
1991
|
-
return;
|
|
1992
|
-
}
|
|
1993
|
-
const runBuildRunner2 = await clack.confirm({
|
|
1994
|
-
message: "Run build_runner after generation?",
|
|
1995
|
-
initialValue: project.hasBuildRunner
|
|
1996
|
-
});
|
|
1997
|
-
if (clack.isCancel(runBuildRunner2)) {
|
|
1998
|
-
clack.cancel("Cancelled");
|
|
1999
|
-
return;
|
|
2000
|
-
}
|
|
2001
1678
|
const genSpinner = clack.spinner();
|
|
2002
1679
|
genSpinner.start("Generating endpoint stack...");
|
|
2003
1680
|
try {
|
|
@@ -2006,19 +1683,7 @@ async function endpointFlow(project) {
|
|
|
2006
1683
|
httpMethod,
|
|
2007
1684
|
endpointPath,
|
|
2008
1685
|
bffApiFile,
|
|
2009
|
-
|
|
2010
|
-
responseFields,
|
|
2011
|
-
requestModelName,
|
|
2012
|
-
requestFields,
|
|
2013
|
-
useCaseName,
|
|
2014
|
-
datasourceFile,
|
|
2015
|
-
datasourceClassName,
|
|
2016
|
-
repositoryInterfaceFile,
|
|
2017
|
-
repositoryInterfaceName: repositoryInterfaceName2,
|
|
2018
|
-
repositoryImplFile,
|
|
2019
|
-
repositoryImplClassName,
|
|
2020
|
-
feature,
|
|
2021
|
-
runBuildRunner: runBuildRunner2
|
|
1686
|
+
useCaseName
|
|
2022
1687
|
});
|
|
2023
1688
|
genSpinner.stop("Endpoint generated");
|
|
2024
1689
|
clack.outro(chalk5.green("Done!"));
|