wlmaker 1.2.8 → 1.2.9
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 +829 -34
- 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
|
|
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((
|
|
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
|
-
|
|
116
|
+
resolve4();
|
|
117
117
|
});
|
|
118
118
|
child.on("error", () => {
|
|
119
|
-
|
|
119
|
+
resolve4();
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
}
|
|
@@ -956,18 +956,453 @@ async function createUseCase(name, tierInput, options) {
|
|
|
956
956
|
}
|
|
957
957
|
|
|
958
958
|
// src/interactive.ts
|
|
959
|
-
import * as
|
|
959
|
+
import * as fs10 from "fs";
|
|
960
960
|
import * as os from "os";
|
|
961
|
-
import * as
|
|
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/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
|
+
// src/core/endpoint-templates.ts
|
|
1039
|
+
function entityTemplate(name, pascal, fields) {
|
|
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");
|
|
1045
|
+
return `class ${pascal}Entity {
|
|
1046
|
+
${props}
|
|
1047
|
+
|
|
1048
|
+
const ${pascal}Entity({
|
|
1049
|
+
${params}
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
`;
|
|
1053
|
+
}
|
|
1054
|
+
function modelTemplate(name, pascal, fields) {
|
|
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`);
|
|
1062
|
+
return `import 'package:json_annotation/json_annotation.dart';
|
|
1063
|
+
import '../../domain/entities/${name}/${name}_entity.dart';
|
|
1064
|
+
|
|
1065
|
+
part '${name}_model.g.dart';
|
|
1066
|
+
|
|
1067
|
+
@JsonSerializable()
|
|
1068
|
+
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}
|
|
1078
|
+
|
|
1079
|
+
factory ${pascal}Model.fromJson(Map<String, dynamic> json) =>
|
|
1080
|
+
_$${pascal}ModelFromJson(json);
|
|
1081
|
+
|
|
1082
|
+
@override
|
|
1083
|
+
Map<String, dynamic> toJson() => _$${pascal}ModelToJson(this);
|
|
1084
|
+
}
|
|
1085
|
+
`;
|
|
1086
|
+
}
|
|
1087
|
+
function requestModelTemplate(name, pascal, fields) {
|
|
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");
|
|
1093
|
+
return `import 'package:json_annotation/json_annotation.dart';
|
|
1094
|
+
|
|
1095
|
+
part '${name}_request_model.g.dart';
|
|
1096
|
+
|
|
1097
|
+
@JsonSerializable()
|
|
1098
|
+
class ${pascal}RequestModel {
|
|
1099
|
+
${props}
|
|
1100
|
+
|
|
1101
|
+
${pascal}RequestModel({
|
|
1102
|
+
${params}
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
factory ${pascal}RequestModel.fromJson(Map<String, dynamic> json) =>
|
|
1106
|
+
_$${pascal}RequestModelFromJson(json);
|
|
1107
|
+
|
|
1108
|
+
Map<String, dynamic> toJson() => _$${pascal}RequestModelToJson(this);
|
|
1109
|
+
}
|
|
1110
|
+
`;
|
|
1111
|
+
}
|
|
1112
|
+
function useCaseTemplate2(name, pascal, method, params, returnType, repositoryInterface) {
|
|
1113
|
+
const hasParams = params.length > 0;
|
|
1114
|
+
const paramsClass = hasParams ? `
|
|
1115
|
+
class Params {
|
|
1116
|
+
${params.map((p) => ` final ${p.type} ${p.name};`).join("\n")}
|
|
1117
|
+
const Params({${params.map((p) => `required this.${p.name}`).join(", ")}});
|
|
1118
|
+
}
|
|
1119
|
+
` : "";
|
|
1120
|
+
const callParams = hasParams ? "Params params" : "";
|
|
1121
|
+
const callReturn = `Future<${returnType}>`;
|
|
1122
|
+
const args = hasParams ? params.map((p) => `params.${p.name}`).join(", ") : "";
|
|
1123
|
+
return `import 'package:dartz/dartz.dart';
|
|
1124
|
+
import '../../repositories/${repositoryInterface}.dart';
|
|
1125
|
+
|
|
1126
|
+
class ${pascal}UseCase {
|
|
1127
|
+
final ${repositoryInterface} _repository;
|
|
1128
|
+
|
|
1129
|
+
${pascal}UseCase(this._repository);
|
|
1130
|
+
${paramsClass}
|
|
1131
|
+
${callReturn} call(${callParams}) async {
|
|
1132
|
+
return await _repository.${method}(${args});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
`;
|
|
1136
|
+
}
|
|
1137
|
+
function retrofitMethod(methodName, path10, httpMethod, params, returnType) {
|
|
1138
|
+
const httpAnnotation = `@${httpMethod.toUpperCase()}('${path10}')`;
|
|
1139
|
+
const paramList = params.map((p) => {
|
|
1140
|
+
if (p.isPath) return `@Path('${p.name}') ${p.type} ${p.name}`;
|
|
1141
|
+
if (p.isBody) return `@Body() ${p.type} ${p.name}`;
|
|
1142
|
+
if (p.isQuery) return `@Query('${p.name}') ${p.type} ${p.name}`;
|
|
1143
|
+
return `${p.type} ${p.name}`;
|
|
1144
|
+
}).join(", ");
|
|
1145
|
+
return `${httpAnnotation}
|
|
1146
|
+
Future<${returnType}> ${methodName}(${paramList});`;
|
|
1147
|
+
}
|
|
1148
|
+
function datasourceMethod(methodName, returnType, params) {
|
|
1149
|
+
const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
|
|
1150
|
+
return `Future<${returnType}> ${methodName}(${paramList}) async {
|
|
1151
|
+
// TODO: implement ${methodName}
|
|
1152
|
+
throw UnimplementedError();
|
|
1153
|
+
}`;
|
|
1154
|
+
}
|
|
1155
|
+
function repositoryInterfaceMethod(methodName, returnType, params) {
|
|
1156
|
+
const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
|
|
1157
|
+
return `Future<${returnType}> ${methodName}(${paramList});`;
|
|
1158
|
+
}
|
|
1159
|
+
function repositoryImplMethod(methodName, returnType, params, datasourceName) {
|
|
1160
|
+
const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
|
|
1161
|
+
const args = params.map((p) => p.name).join(", ");
|
|
1162
|
+
return `@override
|
|
1163
|
+
Future<${returnType}> ${methodName}(${paramList}) async {
|
|
1164
|
+
return await ${datasourceName}.${methodName}(${args});
|
|
1165
|
+
}`;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/core/dart-injector.ts
|
|
1169
|
+
import * as fs8 from "fs";
|
|
1170
|
+
function injectMethod(filePath, className, methodCode) {
|
|
1171
|
+
const content = fs8.readFileSync(filePath, "utf8");
|
|
1172
|
+
const classRegex = new RegExp(`class\\s+${className}\\s*[^{]*\\{`);
|
|
1173
|
+
const classMatch = content.match(classRegex);
|
|
1174
|
+
if (!classMatch) {
|
|
1175
|
+
throw new Error(`Class "${className}" not found in ${filePath}`);
|
|
1176
|
+
}
|
|
1177
|
+
const classStart = content.indexOf(classMatch[0]);
|
|
1178
|
+
let braceCount = 0;
|
|
1179
|
+
let classEnd = -1;
|
|
1180
|
+
let foundOpen = false;
|
|
1181
|
+
for (let i = classStart; i < content.length; i++) {
|
|
1182
|
+
if (content[i] === "{") {
|
|
1183
|
+
braceCount++;
|
|
1184
|
+
foundOpen = true;
|
|
1185
|
+
} else if (content[i] === "}") {
|
|
1186
|
+
braceCount--;
|
|
1187
|
+
if (foundOpen && braceCount === 0) {
|
|
1188
|
+
classEnd = i;
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (classEnd === -1) {
|
|
1194
|
+
throw new Error(`Could not find closing brace for class "${className}" in ${filePath}`);
|
|
1195
|
+
}
|
|
1196
|
+
const methodSignature = methodCode.trim().split("\n")[0].trim();
|
|
1197
|
+
if (content.includes(methodSignature)) {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const indentedMethod = methodCode.split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
1201
|
+
const newContent = content.slice(0, classEnd) + "\n" + indentedMethod + "\n" + content.slice(classEnd);
|
|
1202
|
+
fs8.writeFileSync(filePath, newContent);
|
|
1203
|
+
}
|
|
1204
|
+
function injectImport(filePath, importLine) {
|
|
1205
|
+
const content = fs8.readFileSync(filePath, "utf8");
|
|
1206
|
+
if (content.includes(importLine.trim())) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
const importRegex = /^import\s+[^;]+;/gm;
|
|
1210
|
+
const imports = [...content.matchAll(importRegex)];
|
|
1211
|
+
if (imports.length > 0) {
|
|
1212
|
+
const lastImport = imports[imports.length - 1];
|
|
1213
|
+
const insertPos = lastImport.index + lastImport[0].length;
|
|
1214
|
+
const newContent = content.slice(0, insertPos) + "\n" + importLine + content.slice(insertPos);
|
|
1215
|
+
fs8.writeFileSync(filePath, newContent);
|
|
1216
|
+
} else {
|
|
1217
|
+
fs8.writeFileSync(filePath, importLine + "\n\n" + content);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
function injectExport(filePath, exportLine) {
|
|
1221
|
+
let content = "";
|
|
1222
|
+
if (fs8.existsSync(filePath)) {
|
|
1223
|
+
content = fs8.readFileSync(filePath, "utf8");
|
|
1224
|
+
}
|
|
1225
|
+
if (content.includes(exportLine)) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
1229
|
+
lines.push(exportLine);
|
|
1230
|
+
lines.sort();
|
|
1231
|
+
fs8.writeFileSync(filePath, lines.join("\n") + "\n");
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/core/create-endpoint.ts
|
|
1235
|
+
async function createEndpoint(options) {
|
|
1236
|
+
const lib = path8.join(options.projectRoot, "lib");
|
|
1237
|
+
const pascal = pascalCase4(options.responseModelName);
|
|
1238
|
+
const useCasePascal = pascalCase4(options.useCaseName);
|
|
1239
|
+
const useCaseSnake = options.useCaseName;
|
|
1240
|
+
const hasRequestBody = ["POST", "PUT", "PATCH"].includes(options.httpMethod) && options.requestFields;
|
|
1241
|
+
const pathParams = extractPathParams(options.endpointPath);
|
|
1242
|
+
const methodParams = buildMethodParams(options, pathParams, hasRequestBody);
|
|
1243
|
+
const methodParamsForSignature = methodParams.map((p) => ({ name: p.name, type: p.type }));
|
|
1244
|
+
const returnType = `${pascal}Entity`;
|
|
1245
|
+
const repositoryInterfaceSnake = path8.basename(options.repositoryInterfaceFile, ".dart");
|
|
1246
|
+
const spinner2 = (msg) => console.log(chalk4.cyan(` \u2192 ${msg}`));
|
|
1247
|
+
if (options.responseFields && options.responseFields.length > 0) {
|
|
1248
|
+
spinner2("Generating entity");
|
|
1249
|
+
const entityDir = path8.join(lib, "domain", "entities", options.feature);
|
|
1250
|
+
fs9.mkdirSync(entityDir, { recursive: true });
|
|
1251
|
+
fs9.writeFileSync(
|
|
1252
|
+
path8.join(entityDir, `${options.responseModelName}_entity.dart`),
|
|
1253
|
+
entityTemplate(options.responseModelName, pascal, options.responseFields)
|
|
1254
|
+
);
|
|
1255
|
+
spinner2("Generating model");
|
|
1256
|
+
const modelDir = path8.join(lib, "data", "models", options.feature);
|
|
1257
|
+
fs9.mkdirSync(modelDir, { recursive: true });
|
|
1258
|
+
fs9.writeFileSync(
|
|
1259
|
+
path8.join(modelDir, `${options.responseModelName}_model.dart`),
|
|
1260
|
+
modelTemplate(options.responseModelName, pascal, options.responseFields)
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
if (hasRequestBody && options.requestFields && options.requestModelName) {
|
|
1264
|
+
spinner2("Generating request model");
|
|
1265
|
+
const reqPascal = pascalCase4(options.requestModelName);
|
|
1266
|
+
const reqModelDir = path8.join(lib, "data", "models", options.feature);
|
|
1267
|
+
fs9.mkdirSync(reqModelDir, { recursive: true });
|
|
1268
|
+
fs9.writeFileSync(
|
|
1269
|
+
path8.join(reqModelDir, `${options.requestModelName}_request_model.dart`),
|
|
1270
|
+
requestModelTemplate(options.requestModelName, reqPascal, options.requestFields)
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
spinner2("Injecting Retrofit method");
|
|
1274
|
+
const bffPath = path8.resolve(options.bffApiFile);
|
|
1275
|
+
if (fs9.existsSync(bffPath)) {
|
|
1276
|
+
const retrofitParams = buildRetrofitParams(options, pathParams, hasRequestBody);
|
|
1277
|
+
const retrofitReturnType = `${pascal}Model`;
|
|
1278
|
+
const method = retrofitMethod(
|
|
1279
|
+
camelCase(options.useCaseName),
|
|
1280
|
+
options.endpointPath,
|
|
1281
|
+
options.httpMethod,
|
|
1282
|
+
retrofitParams,
|
|
1283
|
+
retrofitReturnType
|
|
1284
|
+
);
|
|
1285
|
+
injectMethod(bffPath, extractClassName(bffPath), method);
|
|
1286
|
+
const modelImport = `import 'package:${options.feature}/data/models/${options.responseModelName}/${options.responseModelName}_model.dart';`;
|
|
1287
|
+
injectImport(bffPath, modelImport);
|
|
1288
|
+
} else {
|
|
1289
|
+
console.log(chalk4.yellow(` \u26A0 BFF API file not found: ${bffPath}`));
|
|
1290
|
+
}
|
|
1291
|
+
spinner2("Injecting datasource method");
|
|
1292
|
+
const dsPath = path8.resolve(options.datasourceFile);
|
|
1293
|
+
if (fs9.existsSync(dsPath)) {
|
|
1294
|
+
const dsMethod = datasourceMethod(
|
|
1295
|
+
camelCase(options.useCaseName),
|
|
1296
|
+
returnType,
|
|
1297
|
+
methodParamsForSignature
|
|
1298
|
+
);
|
|
1299
|
+
injectMethod(dsPath, options.datasourceClassName, dsMethod);
|
|
1300
|
+
} else {
|
|
1301
|
+
console.log(chalk4.yellow(` \u26A0 Datasource file not found: ${dsPath}`));
|
|
1302
|
+
}
|
|
1303
|
+
spinner2("Injecting repository interface method");
|
|
1304
|
+
const repoIfacePath = path8.resolve(options.repositoryInterfaceFile);
|
|
1305
|
+
if (fs9.existsSync(repoIfacePath)) {
|
|
1306
|
+
const ifaceMethod = repositoryInterfaceMethod(
|
|
1307
|
+
camelCase(options.useCaseName),
|
|
1308
|
+
returnType,
|
|
1309
|
+
methodParamsForSignature
|
|
1310
|
+
);
|
|
1311
|
+
injectMethod(repoIfacePath, options.repositoryInterfaceName, ifaceMethod);
|
|
1312
|
+
const entityImport = `import 'package:${options.feature}/domain/entities/${options.responseModelName}/${options.responseModelName}_entity.dart';`;
|
|
1313
|
+
injectImport(repoIfacePath, entityImport);
|
|
1314
|
+
} else {
|
|
1315
|
+
console.log(chalk4.yellow(` \u26A0 Repository interface not found: ${repoIfacePath}`));
|
|
1316
|
+
}
|
|
1317
|
+
spinner2("Injectating repository implementation method");
|
|
1318
|
+
const repoImplPath = path8.resolve(options.repositoryImplFile);
|
|
1319
|
+
if (fs9.existsSync(repoImplPath)) {
|
|
1320
|
+
const dsVarName = camelCase(options.datasourceClassName);
|
|
1321
|
+
const implMethod = repositoryImplMethod(
|
|
1322
|
+
camelCase(options.useCaseName),
|
|
1323
|
+
returnType,
|
|
1324
|
+
methodParamsForSignature,
|
|
1325
|
+
dsVarName
|
|
1326
|
+
);
|
|
1327
|
+
injectMethod(repoImplPath, options.repositoryImplClassName, implMethod);
|
|
1328
|
+
} else {
|
|
1329
|
+
console.log(chalk4.yellow(` \u26A0 Repository implementation not found: ${repoImplPath}`));
|
|
1330
|
+
}
|
|
1331
|
+
spinner2("Generating UseCase");
|
|
1332
|
+
const useCaseDir = path8.join(lib, "domain", "usecases", options.feature);
|
|
1333
|
+
fs9.mkdirSync(useCaseDir, { recursive: true });
|
|
1334
|
+
fs9.writeFileSync(
|
|
1335
|
+
path8.join(useCaseDir, `${useCaseSnake}_usecase.dart`),
|
|
1336
|
+
useCaseTemplate2(
|
|
1337
|
+
useCaseSnake,
|
|
1338
|
+
useCasePascal,
|
|
1339
|
+
camelCase(options.useCaseName),
|
|
1340
|
+
methodParamsForSignature,
|
|
1341
|
+
returnType,
|
|
1342
|
+
repositoryInterfaceName
|
|
1343
|
+
)
|
|
1344
|
+
);
|
|
1345
|
+
spinner2("Updating barrel file");
|
|
1346
|
+
const barrelPath = path8.join(useCaseDir, "usecases.dart");
|
|
1347
|
+
const exportLine = `export '${useCaseSnake}_usecase.dart';`;
|
|
1348
|
+
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
|
+
console.log(chalk4.green(`
|
|
1360
|
+
\u2713 Endpoint "${options.useCaseName}" generated successfully.`));
|
|
1361
|
+
}
|
|
1362
|
+
function extractPathParams(endpointPath) {
|
|
1363
|
+
const regex = /\{(\w+)\}/g;
|
|
1364
|
+
const params = [];
|
|
1365
|
+
let match;
|
|
1366
|
+
while ((match = regex.exec(endpointPath)) !== null) {
|
|
1367
|
+
params.push({ name: match[1], type: "String" });
|
|
1368
|
+
}
|
|
1369
|
+
return params;
|
|
1370
|
+
}
|
|
1371
|
+
function buildMethodParams(options, pathParams, hasRequestBody) {
|
|
1372
|
+
const params = [...pathParams];
|
|
1373
|
+
if (hasRequestBody && options.requestModelName) {
|
|
1374
|
+
const reqPascal = pascalCase4(options.requestModelName);
|
|
1375
|
+
params.push({ name: "body", type: `${reqPascal}RequestModel` });
|
|
1376
|
+
}
|
|
1377
|
+
return params;
|
|
1378
|
+
}
|
|
1379
|
+
function buildRetrofitParams(options, pathParams, hasRequestBody) {
|
|
1380
|
+
const params = [];
|
|
1381
|
+
for (const pp of pathParams) {
|
|
1382
|
+
params.push({ ...pp, isPath: true });
|
|
1383
|
+
}
|
|
1384
|
+
if (hasRequestBody && options.requestModelName) {
|
|
1385
|
+
const reqPascal = pascalCase4(options.requestModelName);
|
|
1386
|
+
params.push({ name: "body", type: `${reqPascal}RequestModel`, isBody: true });
|
|
1387
|
+
}
|
|
1388
|
+
return params;
|
|
1389
|
+
}
|
|
1390
|
+
function extractClassName(filePath) {
|
|
1391
|
+
const content = fs9.readFileSync(filePath, "utf8");
|
|
1392
|
+
const match = content.match(/class\s+(\w+)\s+/);
|
|
1393
|
+
if (!match) throw new Error(`No class found in ${filePath}`);
|
|
1394
|
+
return match[1];
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// src/interactive.ts
|
|
1398
|
+
import { pascalCase as pascalCase5 } from "change-case";
|
|
964
1399
|
var SNAKE_CASE_REGEX4 = /^[a-z][a-z0-9_]*$/;
|
|
965
1400
|
async function resolveProject() {
|
|
966
1401
|
const s = clack.spinner();
|
|
967
1402
|
s.start("Analyzing current directory...");
|
|
968
1403
|
const cwdProject = analyzeProject(process.cwd());
|
|
969
1404
|
if (cwdProject && (cwdProject.hasFreezed || cwdProject.hasBloc)) {
|
|
970
|
-
s.stop(`Found ${
|
|
1405
|
+
s.stop(`Found ${chalk5.green(cwdProject.projectName)}`);
|
|
971
1406
|
return cwdProject;
|
|
972
1407
|
}
|
|
973
1408
|
s.message("Looking for Melos monorepo...");
|
|
@@ -980,8 +1415,8 @@ async function resolveProject() {
|
|
|
980
1415
|
}
|
|
981
1416
|
}
|
|
982
1417
|
s.message("Scanning for Flutter projects...");
|
|
983
|
-
const homeDev =
|
|
984
|
-
if (
|
|
1418
|
+
const homeDev = path9.join(os.homedir(), "Development");
|
|
1419
|
+
if (fs10.existsSync(homeDev)) {
|
|
985
1420
|
const projects = discoverProjects(homeDev, 2);
|
|
986
1421
|
if (projects.length > 0) {
|
|
987
1422
|
s.stop(`Found ${projects.length} Flutter project(s)`);
|
|
@@ -989,12 +1424,12 @@ async function resolveProject() {
|
|
|
989
1424
|
}
|
|
990
1425
|
}
|
|
991
1426
|
s.stop("No Flutter projects found");
|
|
992
|
-
clack.outro(
|
|
1427
|
+
clack.outro(chalk5.red("Could not find any Flutter project with freezed or flutter_bloc."));
|
|
993
1428
|
return null;
|
|
994
1429
|
}
|
|
995
1430
|
async function selectPackage(projects) {
|
|
996
1431
|
if (projects.length === 1) {
|
|
997
|
-
clack.log.info(`Using ${
|
|
1432
|
+
clack.log.info(`Using ${chalk5.green(projects[0].projectName)}`);
|
|
998
1433
|
return projects[0];
|
|
999
1434
|
}
|
|
1000
1435
|
const selected = await clack.select({
|
|
@@ -1002,7 +1437,7 @@ async function selectPackage(projects) {
|
|
|
1002
1437
|
options: projects.map((p) => ({
|
|
1003
1438
|
value: p,
|
|
1004
1439
|
label: p.projectName,
|
|
1005
|
-
hint:
|
|
1440
|
+
hint: path9.relative(os.homedir(), p.projectRoot)
|
|
1006
1441
|
}))
|
|
1007
1442
|
});
|
|
1008
1443
|
if (clack.isCancel(selected)) {
|
|
@@ -1050,13 +1485,13 @@ async function blocFlow(project) {
|
|
|
1050
1485
|
clack.cancel("Cancelled");
|
|
1051
1486
|
return;
|
|
1052
1487
|
}
|
|
1053
|
-
targetDir =
|
|
1488
|
+
targetDir = path9.resolve(customPath);
|
|
1054
1489
|
} else {
|
|
1055
|
-
targetDir =
|
|
1490
|
+
targetDir = path9.join(project.projectRoot, "lib", "features", feature);
|
|
1056
1491
|
}
|
|
1057
|
-
} else if (
|
|
1058
|
-
targetDir =
|
|
1059
|
-
clack.log.info(`Target: ${
|
|
1492
|
+
} else if (fs10.existsSync(path9.join(project.projectRoot, "lib", "bloc"))) {
|
|
1493
|
+
targetDir = path9.join(project.projectRoot, "lib", "bloc");
|
|
1494
|
+
clack.log.info(`Target: ${chalk5.cyan("lib/bloc/")}`);
|
|
1060
1495
|
} else {
|
|
1061
1496
|
clack.note(
|
|
1062
1497
|
"No lib/features/ directory found. Provide a target path manually.",
|
|
@@ -1073,7 +1508,7 @@ async function blocFlow(project) {
|
|
|
1073
1508
|
clack.cancel("Cancelled");
|
|
1074
1509
|
return;
|
|
1075
1510
|
}
|
|
1076
|
-
targetDir =
|
|
1511
|
+
targetDir = path9.resolve(customPath);
|
|
1077
1512
|
}
|
|
1078
1513
|
const defaultRun = project.hasBuildRunner;
|
|
1079
1514
|
const runBuildRunner2 = await clack.confirm({
|
|
@@ -1092,10 +1527,10 @@ async function blocFlow(project) {
|
|
|
1092
1527
|
buildRunner: runBuildRunner2
|
|
1093
1528
|
});
|
|
1094
1529
|
genSpinner.stop("BLoC generated");
|
|
1095
|
-
clack.outro(
|
|
1530
|
+
clack.outro(chalk5.green("Done!"));
|
|
1096
1531
|
} catch (error) {
|
|
1097
1532
|
genSpinner.stop("Failed");
|
|
1098
|
-
clack.outro(
|
|
1533
|
+
clack.outro(chalk5.red(`Error: ${error}`));
|
|
1099
1534
|
}
|
|
1100
1535
|
}
|
|
1101
1536
|
async function widgetFlow() {
|
|
@@ -1103,14 +1538,14 @@ async function widgetFlow() {
|
|
|
1103
1538
|
const ds = detectDesignSystem(projectRoot);
|
|
1104
1539
|
if (!ds) {
|
|
1105
1540
|
clack.outro(
|
|
1106
|
-
|
|
1541
|
+
chalk5.red(
|
|
1107
1542
|
"No design system detected. Run this command from a project with wl_design_system/ directory."
|
|
1108
1543
|
)
|
|
1109
1544
|
);
|
|
1110
1545
|
return;
|
|
1111
1546
|
}
|
|
1112
1547
|
clack.log.info(
|
|
1113
|
-
`Design system: ${
|
|
1548
|
+
`Design system: ${chalk5.cyan(path9.relative(projectRoot, ds.componentsDir))}`
|
|
1114
1549
|
);
|
|
1115
1550
|
const name = await clack.text({
|
|
1116
1551
|
message: "Widget name (snake_case, without wl_ prefix)",
|
|
@@ -1175,17 +1610,17 @@ async function widgetFlow() {
|
|
|
1175
1610
|
genSpinner.stop(`Use-case skipped: ${e}`);
|
|
1176
1611
|
}
|
|
1177
1612
|
}
|
|
1178
|
-
clack.outro(
|
|
1613
|
+
clack.outro(chalk5.green("Done!"));
|
|
1179
1614
|
} catch (error) {
|
|
1180
1615
|
genSpinner.stop("Failed");
|
|
1181
|
-
clack.outro(
|
|
1616
|
+
clack.outro(chalk5.red(`Error: ${error}`));
|
|
1182
1617
|
}
|
|
1183
1618
|
}
|
|
1184
1619
|
async function useCaseFlow(project) {
|
|
1185
1620
|
const ds = detectDesignSystem(project.projectRoot);
|
|
1186
1621
|
if (!ds) {
|
|
1187
1622
|
clack.outro(
|
|
1188
|
-
|
|
1623
|
+
chalk5.red(
|
|
1189
1624
|
"No design system detected. Ensure wl_design_system/ directory exists."
|
|
1190
1625
|
)
|
|
1191
1626
|
);
|
|
@@ -1193,7 +1628,7 @@ async function useCaseFlow(project) {
|
|
|
1193
1628
|
}
|
|
1194
1629
|
if (!ds.widgetbookDir) {
|
|
1195
1630
|
clack.outro(
|
|
1196
|
-
|
|
1631
|
+
chalk5.red(
|
|
1197
1632
|
"No widgetbook package detected. Ensure apps/widgetbook/ exists."
|
|
1198
1633
|
)
|
|
1199
1634
|
);
|
|
@@ -1242,14 +1677,358 @@ async function useCaseFlow(project) {
|
|
|
1242
1677
|
buildRunner: runBuildRunner2
|
|
1243
1678
|
});
|
|
1244
1679
|
genSpinner.stop("Use-case generated");
|
|
1245
|
-
clack.outro(
|
|
1680
|
+
clack.outro(chalk5.green("Done!"));
|
|
1246
1681
|
} catch (error) {
|
|
1247
1682
|
genSpinner.stop("Failed");
|
|
1248
|
-
clack.outro(
|
|
1683
|
+
clack.outro(chalk5.red(`Error: ${error}`));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
async function endpointFlow(project) {
|
|
1687
|
+
const lib = path9.join(project.projectRoot, "lib");
|
|
1688
|
+
const httpMethod = await clack.select({
|
|
1689
|
+
message: "HTTP Method",
|
|
1690
|
+
options: [
|
|
1691
|
+
{ value: "GET", label: "GET" },
|
|
1692
|
+
{ value: "POST", label: "POST" },
|
|
1693
|
+
{ value: "PUT", label: "PUT" },
|
|
1694
|
+
{ value: "PATCH", label: "PATCH" },
|
|
1695
|
+
{ value: "DELETE", label: "DELETE" }
|
|
1696
|
+
]
|
|
1697
|
+
});
|
|
1698
|
+
if (clack.isCancel(httpMethod)) {
|
|
1699
|
+
clack.cancel("Cancelled");
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
const endpointPath = await clack.text({
|
|
1703
|
+
message: "Endpoint path (e.g. /cp/{zipCode})",
|
|
1704
|
+
placeholder: "/api/users/{id}",
|
|
1705
|
+
validate: (v) => {
|
|
1706
|
+
if (!v.trim()) return "Path is required";
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
if (clack.isCancel(endpointPath)) {
|
|
1710
|
+
clack.cancel("Cancelled");
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
const bffDir = path9.join(lib, "data", "api", "bff");
|
|
1714
|
+
let bffApiFile = "";
|
|
1715
|
+
if (fs10.existsSync(bffDir)) {
|
|
1716
|
+
const dartFiles = fs10.readdirSync(bffDir).filter((f) => f.endsWith(".dart")).sort();
|
|
1717
|
+
if (dartFiles.length > 0) {
|
|
1718
|
+
const selected = await clack.select({
|
|
1719
|
+
message: "Select BFF API file",
|
|
1720
|
+
options: [
|
|
1721
|
+
...dartFiles.map((f) => ({
|
|
1722
|
+
value: path9.join(bffDir, f),
|
|
1723
|
+
label: f
|
|
1724
|
+
})),
|
|
1725
|
+
{ value: "__custom__", label: "Custom path..." }
|
|
1726
|
+
]
|
|
1727
|
+
});
|
|
1728
|
+
if (clack.isCancel(selected)) {
|
|
1729
|
+
clack.cancel("Cancelled");
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (selected === "__custom__") {
|
|
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
|
+
}
|
|
1748
|
+
} else {
|
|
1749
|
+
const customPath = await clack.text({
|
|
1750
|
+
message: "BFF API file path (no .dart files found in bff/)",
|
|
1751
|
+
placeholder: "lib/data/api/bff/my_api.dart",
|
|
1752
|
+
validate: (v) => {
|
|
1753
|
+
if (!v.trim()) return "Path is required";
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
if (clack.isCancel(customPath)) {
|
|
1757
|
+
clack.cancel("Cancelled");
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
bffApiFile = path9.resolve(customPath);
|
|
1761
|
+
}
|
|
1762
|
+
} else {
|
|
1763
|
+
const customPath = await clack.text({
|
|
1764
|
+
message: "BFF API file path (bff/ dir not found)",
|
|
1765
|
+
placeholder: "lib/data/api/bff/my_api.dart",
|
|
1766
|
+
validate: (v) => {
|
|
1767
|
+
if (!v.trim()) return "Path is required";
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
if (clack.isCancel(customPath)) {
|
|
1771
|
+
clack.cancel("Cancelled");
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
bffApiFile = path9.resolve(customPath);
|
|
1775
|
+
}
|
|
1776
|
+
const responseChoice = await clack.select({
|
|
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
|
+
}
|
|
1896
|
+
const pathSegments = endpointPath.replace(/^\//, "").split("/").filter((s) => !s.startsWith("{"));
|
|
1897
|
+
const inferredName = pathSegments.length > 0 ? "get_" + pathSegments.join("_") : "get_data";
|
|
1898
|
+
const useCaseName = await clack.text({
|
|
1899
|
+
message: "UseCase name (snake_case)",
|
|
1900
|
+
placeholder: inferredName,
|
|
1901
|
+
initialValue: inferredName,
|
|
1902
|
+
validate: (v) => {
|
|
1903
|
+
if (!v.trim()) return "Name is required";
|
|
1904
|
+
if (!SNAKE_CASE_REGEX4.test(v)) return "Must be snake_case";
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
if (clack.isCancel(useCaseName)) {
|
|
1908
|
+
clack.cancel("Cancelled");
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
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
|
+
const genSpinner = clack.spinner();
|
|
2002
|
+
genSpinner.start("Generating endpoint stack...");
|
|
2003
|
+
try {
|
|
2004
|
+
await createEndpoint({
|
|
2005
|
+
projectRoot: project.projectRoot,
|
|
2006
|
+
httpMethod,
|
|
2007
|
+
endpointPath,
|
|
2008
|
+
bffApiFile,
|
|
2009
|
+
responseModelName,
|
|
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
|
|
2022
|
+
});
|
|
2023
|
+
genSpinner.stop("Endpoint generated");
|
|
2024
|
+
clack.outro(chalk5.green("Done!"));
|
|
2025
|
+
} catch (error) {
|
|
2026
|
+
genSpinner.stop("Failed");
|
|
2027
|
+
clack.outro(chalk5.red(`Error: ${error}`));
|
|
1249
2028
|
}
|
|
1250
2029
|
}
|
|
1251
2030
|
async function interactiveMode() {
|
|
1252
|
-
clack.intro(
|
|
2031
|
+
clack.intro(chalk5.bgCyan(chalk5.black(" wlmaker ")));
|
|
1253
2032
|
const createType = await clack.select({
|
|
1254
2033
|
message: "What do you want to create?",
|
|
1255
2034
|
options: [
|
|
@@ -1259,6 +2038,11 @@ async function interactiveMode() {
|
|
|
1259
2038
|
value: "usecase",
|
|
1260
2039
|
label: "Widgetbook Use-Case",
|
|
1261
2040
|
hint: "Component showcase"
|
|
2041
|
+
},
|
|
2042
|
+
{
|
|
2043
|
+
value: "endpoint",
|
|
2044
|
+
label: "Endpoint",
|
|
2045
|
+
hint: "BFF Clean Architecture stack"
|
|
1262
2046
|
}
|
|
1263
2047
|
]
|
|
1264
2048
|
});
|
|
@@ -1283,6 +2067,12 @@ async function interactiveMode() {
|
|
|
1283
2067
|
await useCaseFlow(project);
|
|
1284
2068
|
break;
|
|
1285
2069
|
}
|
|
2070
|
+
case "endpoint": {
|
|
2071
|
+
const project = await resolveProject();
|
|
2072
|
+
if (!project) return;
|
|
2073
|
+
await endpointFlow(project);
|
|
2074
|
+
break;
|
|
2075
|
+
}
|
|
1286
2076
|
}
|
|
1287
2077
|
}
|
|
1288
2078
|
|
|
@@ -1302,7 +2092,7 @@ program.command("bloc").description("Create a new BLoC with Freezed sealed class
|
|
|
1302
2092
|
try {
|
|
1303
2093
|
await createBloc(name, options);
|
|
1304
2094
|
} catch (error) {
|
|
1305
|
-
console.error(
|
|
2095
|
+
console.error(chalk6.red(`Error: ${error}`));
|
|
1306
2096
|
process.exit(1);
|
|
1307
2097
|
}
|
|
1308
2098
|
}
|
|
@@ -1319,12 +2109,12 @@ program.command("widget").description("Create a new widget in the design system"
|
|
|
1319
2109
|
projectRoot: options.dir,
|
|
1320
2110
|
buildRunner: false
|
|
1321
2111
|
});
|
|
1322
|
-
console.log(
|
|
2112
|
+
console.log(chalk6.green("Widgetbook use-case created"));
|
|
1323
2113
|
} catch {
|
|
1324
|
-
console.log(
|
|
2114
|
+
console.log(chalk6.yellow("Use-case skipped (may already exist)"));
|
|
1325
2115
|
}
|
|
1326
2116
|
} catch (error) {
|
|
1327
|
-
console.error(
|
|
2117
|
+
console.error(chalk6.red(`Error: ${error}`));
|
|
1328
2118
|
process.exit(1);
|
|
1329
2119
|
}
|
|
1330
2120
|
}
|
|
@@ -1337,11 +2127,16 @@ program.command("usecase").description("Create a Widgetbook use-case for an exis
|
|
|
1337
2127
|
buildRunner: options.buildRunner
|
|
1338
2128
|
});
|
|
1339
2129
|
} catch (error) {
|
|
1340
|
-
console.error(
|
|
2130
|
+
console.error(chalk6.red(`Error: ${error}`));
|
|
1341
2131
|
process.exit(1);
|
|
1342
2132
|
}
|
|
1343
2133
|
}
|
|
1344
2134
|
);
|
|
2135
|
+
program.command("endpoint").description("Generate Clean Architecture stack for a BFF endpoint").action(async () => {
|
|
2136
|
+
const project = await resolveProject();
|
|
2137
|
+
if (!project) return;
|
|
2138
|
+
await endpointFlow(project);
|
|
2139
|
+
});
|
|
1345
2140
|
program.action(async () => {
|
|
1346
2141
|
await interactiveMode();
|
|
1347
2142
|
});
|