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.
- package/dist/cli.mjs +494 -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,357 @@ 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/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 ${
|
|
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 =
|
|
984
|
-
if (
|
|
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(
|
|
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 ${
|
|
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:
|
|
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 =
|
|
1392
|
+
targetDir = path9.resolve(customPath);
|
|
1054
1393
|
} else {
|
|
1055
|
-
targetDir =
|
|
1394
|
+
targetDir = path9.join(project.projectRoot, "lib", "features", feature);
|
|
1056
1395
|
}
|
|
1057
|
-
} else if (
|
|
1058
|
-
targetDir =
|
|
1059
|
-
clack.log.info(`Target: ${
|
|
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 =
|
|
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(
|
|
1434
|
+
clack.outro(chalk5.green("Done!"));
|
|
1096
1435
|
} catch (error) {
|
|
1097
1436
|
genSpinner.stop("Failed");
|
|
1098
|
-
clack.outro(
|
|
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
|
-
|
|
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: ${
|
|
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(
|
|
1517
|
+
clack.outro(chalk5.green("Done!"));
|
|
1179
1518
|
} catch (error) {
|
|
1180
1519
|
genSpinner.stop("Failed");
|
|
1181
|
-
clack.outro(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
1692
|
+
clack.outro(chalk5.red(`Error: ${error}`));
|
|
1249
1693
|
}
|
|
1250
1694
|
}
|
|
1251
1695
|
async function interactiveMode() {
|
|
1252
|
-
clack.intro(
|
|
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(
|
|
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(
|
|
1777
|
+
console.log(chalk6.green("Widgetbook use-case created"));
|
|
1323
1778
|
} catch {
|
|
1324
|
-
console.log(
|
|
1779
|
+
console.log(chalk6.yellow("Use-case skipped (may already exist)"));
|
|
1325
1780
|
}
|
|
1326
1781
|
} catch (error) {
|
|
1327
|
-
console.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(
|
|
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
|
});
|