wlmaker 1.2.12 → 1.3.0
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 +680 -97
- package/package.json +3 -2
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 chalk9 from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/core/create-bloc.ts
|
|
9
9
|
import * as fs3 from "fs";
|
|
@@ -956,24 +956,30 @@ async function createUseCase(name, tierInput, options) {
|
|
|
956
956
|
}
|
|
957
957
|
|
|
958
958
|
// src/interactive.ts
|
|
959
|
-
import * as
|
|
959
|
+
import * as fs13 from "fs";
|
|
960
960
|
import * as os from "os";
|
|
961
|
-
import * as
|
|
961
|
+
import * as path12 from "path";
|
|
962
962
|
import * as clack from "@clack/prompts";
|
|
963
|
-
import
|
|
963
|
+
import chalk8 from "chalk";
|
|
964
964
|
|
|
965
965
|
// src/core/create-endpoint.ts
|
|
966
966
|
import * as fs9 from "fs";
|
|
967
967
|
import * as path8 from "path";
|
|
968
968
|
import chalk4 from "chalk";
|
|
969
|
-
import { pascalCase as pascalCase4, camelCase } from "change-case";
|
|
969
|
+
import { pascalCase as pascalCase4, camelCase as camelCase2 } from "change-case";
|
|
970
970
|
|
|
971
971
|
// src/core/endpoint-templates.ts
|
|
972
|
+
import { camelCase } from "change-case";
|
|
972
973
|
function entityBoilerplate(pascal) {
|
|
973
|
-
return `
|
|
974
|
+
return `import 'package:equatable/equatable.dart';
|
|
975
|
+
|
|
976
|
+
class ${pascal}Entity extends Equatable {
|
|
974
977
|
// TODO: Define fields
|
|
975
978
|
|
|
976
979
|
const ${pascal}Entity();
|
|
980
|
+
|
|
981
|
+
@override
|
|
982
|
+
List<Object?> get props => [];
|
|
977
983
|
}
|
|
978
984
|
`;
|
|
979
985
|
}
|
|
@@ -1019,33 +1025,32 @@ class ${pascal}RequestModel {
|
|
|
1019
1025
|
}
|
|
1020
1026
|
`;
|
|
1021
1027
|
}
|
|
1022
|
-
function useCaseTemplate2(
|
|
1028
|
+
function useCaseTemplate2(pascal, method, params, returnType, repositoryInterface, projectName) {
|
|
1023
1029
|
const hasParams = params.length > 0;
|
|
1024
1030
|
const paramsClass = hasParams ? `
|
|
1025
1031
|
class Params {
|
|
1026
|
-
${params.map((p) => ` final ${p.type} ${p.name};`).join("\n")}
|
|
1027
1032
|
const Params({${params.map((p) => `required this.${p.name}`).join(", ")}});
|
|
1033
|
+
${params.map((p) => ` final ${p.type} ${p.name};`).join("\n")}
|
|
1028
1034
|
}
|
|
1029
1035
|
` : "";
|
|
1030
1036
|
const callParams = hasParams ? "Params params" : "";
|
|
1031
1037
|
const callReturn = `Future<${returnType}>`;
|
|
1032
1038
|
const args = hasParams ? params.map((p) => `params.${p.name}`).join(", ") : "";
|
|
1033
|
-
return `import 'package
|
|
1034
|
-
import '../../repositories/${repositoryInterface}.dart';
|
|
1039
|
+
return `import 'package:${projectName}/core.dart';
|
|
1035
1040
|
|
|
1036
1041
|
class ${pascal}UseCase {
|
|
1037
|
-
final ${repositoryInterface} _repository;
|
|
1038
|
-
|
|
1039
1042
|
${pascal}UseCase(this._repository);
|
|
1043
|
+
|
|
1044
|
+
final ${repositoryInterface} _repository;
|
|
1040
1045
|
${paramsClass}
|
|
1041
1046
|
${callReturn} call(${callParams}) async {
|
|
1042
|
-
return
|
|
1047
|
+
return _repository.${method}(${args});
|
|
1043
1048
|
}
|
|
1044
1049
|
}
|
|
1045
1050
|
`;
|
|
1046
1051
|
}
|
|
1047
|
-
function retrofitMethod(methodName,
|
|
1048
|
-
const httpAnnotation = `@${httpMethod.toUpperCase()}('${
|
|
1052
|
+
function retrofitMethod(methodName, path13, httpMethod, params, returnType) {
|
|
1053
|
+
const httpAnnotation = `@${httpMethod.toUpperCase()}('${path13}')`;
|
|
1049
1054
|
const paramList = params.map((p) => {
|
|
1050
1055
|
if (p.isPath) return `@Path('${p.name}') ${p.type} ${p.name}`;
|
|
1051
1056
|
if (p.isBody) return `@Body() ${p.type} ${p.name}`;
|
|
@@ -1066,20 +1071,58 @@ function repositoryInterfaceMethod(methodName, returnType, params) {
|
|
|
1066
1071
|
const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
|
|
1067
1072
|
return `Future<${returnType}> ${methodName}(${paramList});`;
|
|
1068
1073
|
}
|
|
1069
|
-
function repositoryImplMethod(methodName, returnType,
|
|
1074
|
+
function repositoryImplMethod(methodName, returnType, _modelType, params, _datasourceName) {
|
|
1070
1075
|
const paramList = params.map((p) => `${p.type} ${p.name}`).join(", ");
|
|
1071
|
-
const args = params.map((p) => p.name).join(", ");
|
|
1072
1076
|
return `@override
|
|
1073
|
-
Future<${returnType}> ${methodName}(${paramList})
|
|
1074
|
-
|
|
1075
|
-
|
|
1077
|
+
Future<${returnType}> ${methodName}(${paramList}) {
|
|
1078
|
+
// TODO: implement ${methodName}
|
|
1079
|
+
throw UnimplementedError();
|
|
1076
1080
|
}`;
|
|
1077
1081
|
}
|
|
1082
|
+
function datasourceModuleRegistration(domainPascal, lazy = true) {
|
|
1083
|
+
const dsClass = `${domainPascal}RestDataSource`;
|
|
1084
|
+
const dsCamel = camelCase(dsClass);
|
|
1085
|
+
const apiClass = `Bff${domainPascal}Api`;
|
|
1086
|
+
const annotation = lazy ? "@lazySingleton\n" : "";
|
|
1087
|
+
return `//============================================================================
|
|
1088
|
+
// ${domainPascal}
|
|
1089
|
+
//============================================================================
|
|
1090
|
+
${annotation}${dsClass} ${dsCamel}(${apiClass} api) =>
|
|
1091
|
+
${dsClass}(api: api);`;
|
|
1092
|
+
}
|
|
1093
|
+
function repositoryModuleRegistration(domainPascal, lazy = true) {
|
|
1094
|
+
const repoInterface = `${domainPascal}Repository`;
|
|
1095
|
+
const repoCamel = camelCase(repoInterface);
|
|
1096
|
+
const repoImpl = `${domainPascal}RepositoryData`;
|
|
1097
|
+
const dsClass = `${domainPascal}RestDataSource`;
|
|
1098
|
+
const annotation = lazy ? "@lazySingleton\n" : "";
|
|
1099
|
+
return `//============================================================================
|
|
1100
|
+
// ${domainPascal}
|
|
1101
|
+
//============================================================================
|
|
1102
|
+
${annotation}${repoInterface} ${repoCamel}(
|
|
1103
|
+
${dsClass} restDataSource,
|
|
1104
|
+
) => ${repoImpl}(restDataSource: restDataSource);`;
|
|
1105
|
+
}
|
|
1106
|
+
function useCaseModuleRegistration(useCasePascal, domainPascal, lazy = true) {
|
|
1107
|
+
const useCaseClass = `${useCasePascal}UseCase`;
|
|
1108
|
+
const useCaseCamel = camelCase(useCaseClass);
|
|
1109
|
+
const repoInterface = `${domainPascal}Repository`;
|
|
1110
|
+
const annotation = lazy ? "@lazySingleton\n" : "";
|
|
1111
|
+
return `//============================================================================
|
|
1112
|
+
// ${domainPascal}
|
|
1113
|
+
//============================================================================
|
|
1114
|
+
${annotation}${useCaseClass} ${useCaseCamel}(${repoInterface} repository) =>
|
|
1115
|
+
${useCaseClass}(repository);`;
|
|
1116
|
+
}
|
|
1078
1117
|
|
|
1079
1118
|
// src/core/dart-injector.ts
|
|
1080
1119
|
import * as fs8 from "fs";
|
|
1081
|
-
function injectMethod(filePath, className, methodCode) {
|
|
1120
|
+
function injectMethod(filePath, className, methodCode, dedupKey) {
|
|
1082
1121
|
const content = fs8.readFileSync(filePath, "utf8");
|
|
1122
|
+
const dedup = dedupKey ?? findSignature(methodCode);
|
|
1123
|
+
if (dedup && content.includes(dedup)) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1083
1126
|
const classRegex = new RegExp(`class\\s+${className}\\s*[^{]*\\{`);
|
|
1084
1127
|
const classMatch = content.match(classRegex);
|
|
1085
1128
|
if (!classMatch) {
|
|
@@ -1104,14 +1147,26 @@ function injectMethod(filePath, className, methodCode) {
|
|
|
1104
1147
|
if (classEnd === -1) {
|
|
1105
1148
|
throw new Error(`Could not find closing brace for class "${className}" in ${filePath}`);
|
|
1106
1149
|
}
|
|
1107
|
-
const methodSignature = methodCode.trim().split("\n")[0].trim();
|
|
1108
|
-
if (content.includes(methodSignature)) {
|
|
1109
|
-
return;
|
|
1110
|
-
}
|
|
1111
1150
|
const indentedMethod = methodCode.split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
1112
1151
|
const newContent = content.slice(0, classEnd) + "\n" + indentedMethod + "\n" + content.slice(classEnd);
|
|
1113
1152
|
fs8.writeFileSync(filePath, newContent);
|
|
1114
1153
|
}
|
|
1154
|
+
function injectImport(filePath, importLine) {
|
|
1155
|
+
const content = fs8.readFileSync(filePath, "utf8");
|
|
1156
|
+
if (content.includes(importLine.trim())) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const importRegex = /^import\s+[^;]+;/gm;
|
|
1160
|
+
const imports = [...content.matchAll(importRegex)];
|
|
1161
|
+
if (imports.length > 0) {
|
|
1162
|
+
const lastImport = imports[imports.length - 1];
|
|
1163
|
+
const insertPos = lastImport.index + lastImport[0].length;
|
|
1164
|
+
const newContent = content.slice(0, insertPos) + "\n" + importLine + content.slice(insertPos);
|
|
1165
|
+
fs8.writeFileSync(filePath, newContent);
|
|
1166
|
+
} else {
|
|
1167
|
+
fs8.writeFileSync(filePath, importLine + "\n\n" + content);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1115
1170
|
function injectExport(filePath, exportLine) {
|
|
1116
1171
|
let content = "";
|
|
1117
1172
|
if (fs8.existsSync(filePath)) {
|
|
@@ -1125,6 +1180,16 @@ function injectExport(filePath, exportLine) {
|
|
|
1125
1180
|
lines.sort();
|
|
1126
1181
|
fs8.writeFileSync(filePath, lines.join("\n") + "\n");
|
|
1127
1182
|
}
|
|
1183
|
+
function findSignature(methodCode) {
|
|
1184
|
+
const lines = methodCode.trim().split("\n");
|
|
1185
|
+
for (const line of lines) {
|
|
1186
|
+
const stripped = line.trim();
|
|
1187
|
+
if (stripped && !stripped.startsWith("@")) {
|
|
1188
|
+
return stripped;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1128
1193
|
|
|
1129
1194
|
// src/core/create-endpoint.ts
|
|
1130
1195
|
async function createEndpoint(options) {
|
|
@@ -1146,7 +1211,6 @@ async function createEndpoint(options) {
|
|
|
1146
1211
|
const methodParamsForSignature = methodParams.map((p) => ({ name: p.name, type: p.type }));
|
|
1147
1212
|
const modelType = `${pascal}Model`;
|
|
1148
1213
|
const entityType = `${pascal}Entity`;
|
|
1149
|
-
const repositoryInterfaceSnake = path8.basename(repositoryInterfaceFile, ".dart");
|
|
1150
1214
|
const spinner2 = (msg) => console.log(chalk4.cyan(` \u2192 ${msg}`));
|
|
1151
1215
|
spinner2("Generating entity");
|
|
1152
1216
|
const entityDir = path8.join(lib, "domain", "entities", feature);
|
|
@@ -1176,7 +1240,7 @@ async function createEndpoint(options) {
|
|
|
1176
1240
|
if (fs9.existsSync(bffPath)) {
|
|
1177
1241
|
const retrofitParams = buildRetrofitParams(options, pathParams, needsBody);
|
|
1178
1242
|
const method = retrofitMethod(
|
|
1179
|
-
|
|
1243
|
+
camelCase2(options.useCaseName),
|
|
1180
1244
|
options.endpointPath,
|
|
1181
1245
|
options.httpMethod,
|
|
1182
1246
|
retrofitParams,
|
|
@@ -1190,7 +1254,7 @@ async function createEndpoint(options) {
|
|
|
1190
1254
|
const dsPath = path8.resolve(datasourceFile);
|
|
1191
1255
|
if (fs9.existsSync(dsPath)) {
|
|
1192
1256
|
const dsMethod = datasourceMethod(
|
|
1193
|
-
|
|
1257
|
+
camelCase2(options.useCaseName),
|
|
1194
1258
|
modelType,
|
|
1195
1259
|
methodParamsForSignature
|
|
1196
1260
|
);
|
|
@@ -1202,7 +1266,7 @@ async function createEndpoint(options) {
|
|
|
1202
1266
|
const repoIfacePath = path8.resolve(repositoryInterfaceFile);
|
|
1203
1267
|
if (fs9.existsSync(repoIfacePath)) {
|
|
1204
1268
|
const ifaceMethod = repositoryInterfaceMethod(
|
|
1205
|
-
|
|
1269
|
+
camelCase2(options.useCaseName),
|
|
1206
1270
|
entityType,
|
|
1207
1271
|
methodParamsForSignature
|
|
1208
1272
|
);
|
|
@@ -1213,9 +1277,9 @@ async function createEndpoint(options) {
|
|
|
1213
1277
|
spinner2("Injecting repository implementation method");
|
|
1214
1278
|
const repoImplPath = path8.resolve(repositoryImplFile);
|
|
1215
1279
|
if (fs9.existsSync(repoImplPath)) {
|
|
1216
|
-
const dsVarName =
|
|
1280
|
+
const dsVarName = camelCase2(datasourceClassName);
|
|
1217
1281
|
const implMethod = repositoryImplMethod(
|
|
1218
|
-
|
|
1282
|
+
camelCase2(options.useCaseName),
|
|
1219
1283
|
entityType,
|
|
1220
1284
|
modelType,
|
|
1221
1285
|
methodParamsForSignature,
|
|
@@ -1231,15 +1295,20 @@ async function createEndpoint(options) {
|
|
|
1231
1295
|
fs9.writeFileSync(
|
|
1232
1296
|
path8.join(useCaseDir, `${useCaseSnake}_usecase.dart`),
|
|
1233
1297
|
useCaseTemplate2(
|
|
1234
|
-
useCaseSnake,
|
|
1235
1298
|
useCasePascal,
|
|
1236
|
-
|
|
1299
|
+
camelCase2(options.useCaseName),
|
|
1237
1300
|
methodParamsForSignature,
|
|
1238
1301
|
entityType,
|
|
1239
|
-
|
|
1302
|
+
repositoryInterfaceName,
|
|
1303
|
+
options.projectName
|
|
1240
1304
|
)
|
|
1241
1305
|
);
|
|
1242
1306
|
spinner2("Updating barrel files");
|
|
1307
|
+
const dsFileName = path8.basename(datasourceFile);
|
|
1308
|
+
injectExport(
|
|
1309
|
+
path8.join(lib, "data", "datasources", "datasources.dart"),
|
|
1310
|
+
`export '${dsFileName}';`
|
|
1311
|
+
);
|
|
1243
1312
|
const entityBarrel = path8.join(lib, "domain", "entities", feature, `${feature}.dart`);
|
|
1244
1313
|
injectExport(entityBarrel, `export '${useCaseSnake}_entity.dart';`);
|
|
1245
1314
|
injectExport(
|
|
@@ -1261,6 +1330,44 @@ async function createEndpoint(options) {
|
|
|
1261
1330
|
path8.join(lib, "domain", "usecases", "usecases.dart"),
|
|
1262
1331
|
`export '${feature}/${feature}.dart';`
|
|
1263
1332
|
);
|
|
1333
|
+
if (options.diTarget && options.diTarget !== "none") {
|
|
1334
|
+
spinner2("Registering in DI modules");
|
|
1335
|
+
const appBaseDir = findAppBasePackageDir(options.projectRoot, options.diTarget);
|
|
1336
|
+
if (appBaseDir) {
|
|
1337
|
+
const domainPascal = pascalCase4(domain);
|
|
1338
|
+
const lazy = options.diLazySingleton !== false;
|
|
1339
|
+
const dsModuleFile = path8.join(appBaseDir, "datasources_module.dart");
|
|
1340
|
+
const dsRegistration = datasourceModuleRegistration(domainPascal, lazy);
|
|
1341
|
+
injectModuleRegistration(
|
|
1342
|
+
dsModuleFile,
|
|
1343
|
+
"DataSourceModule",
|
|
1344
|
+
dsRegistration,
|
|
1345
|
+
`${domainPascal}RestDataSource`
|
|
1346
|
+
);
|
|
1347
|
+
const repoModuleFile = path8.join(appBaseDir, "repositories_module.dart");
|
|
1348
|
+
const repoRegistration = repositoryModuleRegistration(domainPascal, lazy);
|
|
1349
|
+
injectModuleRegistration(
|
|
1350
|
+
repoModuleFile,
|
|
1351
|
+
"RepositoriesModule",
|
|
1352
|
+
repoRegistration,
|
|
1353
|
+
`${domainPascal}RepositoryData`
|
|
1354
|
+
);
|
|
1355
|
+
const ucModuleFile = path8.join(appBaseDir, "usecases_module.dart");
|
|
1356
|
+
const ucRegistration = useCaseModuleRegistration(useCasePascal, domainPascal, lazy);
|
|
1357
|
+
injectModuleRegistration(
|
|
1358
|
+
ucModuleFile,
|
|
1359
|
+
"UseCasesModule",
|
|
1360
|
+
ucRegistration,
|
|
1361
|
+
`${useCasePascal}UseCase`
|
|
1362
|
+
);
|
|
1363
|
+
const coreImport = `import 'package:${options.projectName}/core.dart';`;
|
|
1364
|
+
injectImport(dsModuleFile, coreImport);
|
|
1365
|
+
injectImport(repoModuleFile, coreImport);
|
|
1366
|
+
injectImport(ucModuleFile, coreImport);
|
|
1367
|
+
} else {
|
|
1368
|
+
console.log(chalk4.yellow(` \u26A0 Package "${options.diTarget}" not found in monorepo`));
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1264
1371
|
console.log(chalk4.green(`
|
|
1265
1372
|
\u2713 Endpoint "${options.useCaseName}" generated successfully.`));
|
|
1266
1373
|
}
|
|
@@ -1304,6 +1411,341 @@ function extractClassName(filePath) {
|
|
|
1304
1411
|
if (!match) throw new Error(`No class found in ${filePath}`);
|
|
1305
1412
|
return match[1];
|
|
1306
1413
|
}
|
|
1414
|
+
function findAppBasePackageDir(projectRoot, packageName) {
|
|
1415
|
+
let dir = path8.resolve(projectRoot);
|
|
1416
|
+
for (let i = 0; i < 10; i++) {
|
|
1417
|
+
const candidate = path8.join(dir, "packages", packageName, "lib", "dependencies");
|
|
1418
|
+
if (fs9.existsSync(candidate)) {
|
|
1419
|
+
return candidate;
|
|
1420
|
+
}
|
|
1421
|
+
const parent = path8.dirname(dir);
|
|
1422
|
+
if (parent === dir) break;
|
|
1423
|
+
dir = parent;
|
|
1424
|
+
}
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
function injectModuleRegistration(filePath, className, registrationCode, dedupKey) {
|
|
1428
|
+
if (!fs9.existsSync(filePath)) {
|
|
1429
|
+
console.log(chalk4.yellow(` \u26A0 Module file not found: ${filePath}`));
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
const content = fs9.readFileSync(filePath, "utf8");
|
|
1433
|
+
if (content.includes(dedupKey)) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const classRegex = new RegExp(`class\\s+${className}\\s*[^{]*\\{`);
|
|
1437
|
+
const classMatch = content.match(classRegex);
|
|
1438
|
+
if (!classMatch) {
|
|
1439
|
+
console.log(chalk4.yellow(` \u26A0 Class "${className}" not found in ${filePath}`));
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
const classStart = content.indexOf(classMatch[0]);
|
|
1443
|
+
let braceCount = 0;
|
|
1444
|
+
let classEnd = -1;
|
|
1445
|
+
let foundOpen = false;
|
|
1446
|
+
for (let i = classStart; i < content.length; i++) {
|
|
1447
|
+
if (content[i] === "{") {
|
|
1448
|
+
braceCount++;
|
|
1449
|
+
foundOpen = true;
|
|
1450
|
+
} else if (content[i] === "}") {
|
|
1451
|
+
braceCount--;
|
|
1452
|
+
if (foundOpen && braceCount === 0) {
|
|
1453
|
+
classEnd = i;
|
|
1454
|
+
break;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (classEnd === -1) {
|
|
1459
|
+
console.log(chalk4.yellow(` \u26A0 Could not find closing brace for "${className}" in ${filePath}`));
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
const indentedCode = registrationCode.split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
1463
|
+
const newContent = content.slice(0, classEnd) + "\n" + indentedCode + "\n" + content.slice(classEnd);
|
|
1464
|
+
fs9.writeFileSync(filePath, newContent);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// src/core/docs-serve.ts
|
|
1468
|
+
import { spawn as spawn2 } from "child_process";
|
|
1469
|
+
import * as fs10 from "fs";
|
|
1470
|
+
import * as path9 from "path";
|
|
1471
|
+
import chalk5 from "chalk";
|
|
1472
|
+
function detectBookDir(startDir) {
|
|
1473
|
+
const root = findMonorepoRoot(startDir) ?? startDir;
|
|
1474
|
+
const bookDir = path9.join(root, "book");
|
|
1475
|
+
if (fs10.existsSync(bookDir) && (fs10.existsSync(path9.join(bookDir, "docusaurus.config.js")) || fs10.existsSync(path9.join(bookDir, "docusaurus.config.ts")) || fs10.existsSync(path9.join(bookDir, "docusaurus.config.mjs")))) {
|
|
1476
|
+
return bookDir;
|
|
1477
|
+
}
|
|
1478
|
+
return void 0;
|
|
1479
|
+
}
|
|
1480
|
+
function serveBook(bookDir) {
|
|
1481
|
+
return new Promise((resolve4, reject) => {
|
|
1482
|
+
const nodeModules = path9.join(bookDir, "node_modules");
|
|
1483
|
+
if (!fs10.existsSync(nodeModules)) {
|
|
1484
|
+
console.log(chalk5.cyan("Installing book dependencies..."));
|
|
1485
|
+
const install = spawn2("npm", ["install"], {
|
|
1486
|
+
cwd: bookDir,
|
|
1487
|
+
stdio: "inherit"
|
|
1488
|
+
});
|
|
1489
|
+
install.on("close", (code) => {
|
|
1490
|
+
if (code !== 0) {
|
|
1491
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
startDevServer(bookDir, resolve4);
|
|
1495
|
+
});
|
|
1496
|
+
install.on("error", (err) => {
|
|
1497
|
+
reject(err);
|
|
1498
|
+
});
|
|
1499
|
+
} else {
|
|
1500
|
+
startDevServer(bookDir, resolve4);
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
function startDevServer(bookDir, done) {
|
|
1505
|
+
const child = spawn2("npm", ["run", "start"], {
|
|
1506
|
+
cwd: bookDir,
|
|
1507
|
+
stdio: "inherit"
|
|
1508
|
+
});
|
|
1509
|
+
child.on("close", () => {
|
|
1510
|
+
done();
|
|
1511
|
+
});
|
|
1512
|
+
child.on("error", () => {
|
|
1513
|
+
done();
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// src/core/docs-commands.ts
|
|
1518
|
+
import * as fs11 from "fs";
|
|
1519
|
+
import * as path10 from "path";
|
|
1520
|
+
import chalk6 from "chalk";
|
|
1521
|
+
import YAML2 from "yaml";
|
|
1522
|
+
function parseMakefile(filePath) {
|
|
1523
|
+
if (!fs11.existsSync(filePath)) return [];
|
|
1524
|
+
const content = fs11.readFileSync(filePath, "utf8");
|
|
1525
|
+
const lines = content.split("\n");
|
|
1526
|
+
const commands = [];
|
|
1527
|
+
const source = path10.basename(filePath);
|
|
1528
|
+
let pendingComments = [];
|
|
1529
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1530
|
+
const line = lines[i];
|
|
1531
|
+
if (line.startsWith("#")) {
|
|
1532
|
+
const commentText = line.replace(/^#+\s?/, "").trim();
|
|
1533
|
+
if (commentText) pendingComments.push(commentText);
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
const targetMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:/);
|
|
1537
|
+
if (targetMatch) {
|
|
1538
|
+
const name = targetMatch[1];
|
|
1539
|
+
if (name.startsWith(".") || name === "Makefile") {
|
|
1540
|
+
pendingComments = [];
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
commands.push({
|
|
1544
|
+
name,
|
|
1545
|
+
description: pendingComments.join(" ").trim() || "",
|
|
1546
|
+
source: "Makefile",
|
|
1547
|
+
file: source
|
|
1548
|
+
});
|
|
1549
|
+
pendingComments = [];
|
|
1550
|
+
} else if (line.trim() !== "") {
|
|
1551
|
+
pendingComments = [];
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return commands;
|
|
1555
|
+
}
|
|
1556
|
+
function parseMelosScripts(filePath) {
|
|
1557
|
+
if (!fs11.existsSync(filePath)) return [];
|
|
1558
|
+
const content = fs11.readFileSync(filePath, "utf8");
|
|
1559
|
+
const parsed = YAML2.parse(content);
|
|
1560
|
+
const scripts = parsed?.scripts;
|
|
1561
|
+
if (!scripts || typeof scripts !== "object") return [];
|
|
1562
|
+
const commands = [];
|
|
1563
|
+
const source = path10.basename(filePath);
|
|
1564
|
+
for (const [name, value] of Object.entries(scripts)) {
|
|
1565
|
+
let description = "";
|
|
1566
|
+
let command = "";
|
|
1567
|
+
if (typeof value === "string") {
|
|
1568
|
+
command = value;
|
|
1569
|
+
} else if (typeof value === "object" && value !== null) {
|
|
1570
|
+
description = value.description ?? "";
|
|
1571
|
+
command = value.run ?? value.exec ?? "";
|
|
1572
|
+
}
|
|
1573
|
+
commands.push({
|
|
1574
|
+
name,
|
|
1575
|
+
description: description || command,
|
|
1576
|
+
source: "melos",
|
|
1577
|
+
file: source
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
return commands;
|
|
1581
|
+
}
|
|
1582
|
+
function discoverCommands(startDir) {
|
|
1583
|
+
const root = findMonorepoRoot(startDir) ?? startDir;
|
|
1584
|
+
const commands = [];
|
|
1585
|
+
const rootMakefile = path10.join(root, "Makefile");
|
|
1586
|
+
commands.push(...parseMakefile(rootMakefile));
|
|
1587
|
+
const melosFile = path10.join(root, "melos.yaml");
|
|
1588
|
+
commands.push(...parseMelosScripts(melosFile));
|
|
1589
|
+
const bookMakefile = path10.join(root, "book", "Makefile");
|
|
1590
|
+
commands.push(...parseMakefile(bookMakefile));
|
|
1591
|
+
return commands;
|
|
1592
|
+
}
|
|
1593
|
+
function displayCommands(commands) {
|
|
1594
|
+
if (commands.length === 0) {
|
|
1595
|
+
console.log(chalk6.yellow("No commands found."));
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1599
|
+
for (const cmd of commands) {
|
|
1600
|
+
const key = `${cmd.source} (${cmd.file})`;
|
|
1601
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
1602
|
+
groups.get(key).push(cmd);
|
|
1603
|
+
}
|
|
1604
|
+
for (const [group, entries] of groups) {
|
|
1605
|
+
console.log(chalk6.bold(chalk6.cyan(`
|
|
1606
|
+
${group}`)));
|
|
1607
|
+
console.log(chalk6.cyan("\u2500".repeat(group.length)));
|
|
1608
|
+
const maxNameLen = Math.max(...entries.map((e) => e.name.length));
|
|
1609
|
+
for (const entry of entries) {
|
|
1610
|
+
const name = chalk6.white(entry.name.padEnd(maxNameLen + 2));
|
|
1611
|
+
const desc = entry.description ? chalk6.gray(entry.description) : chalk6.dim("(no description)");
|
|
1612
|
+
console.log(` ${name} ${desc}`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
console.log();
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// src/core/docs-architecture.ts
|
|
1619
|
+
import * as fs12 from "fs";
|
|
1620
|
+
import * as path11 from "path";
|
|
1621
|
+
import chalk7 from "chalk";
|
|
1622
|
+
import YAML3 from "yaml";
|
|
1623
|
+
var KEY_DEPS = [
|
|
1624
|
+
"freezed",
|
|
1625
|
+
"freezed_annotation",
|
|
1626
|
+
"flutter_bloc",
|
|
1627
|
+
"bloc",
|
|
1628
|
+
"retrofit",
|
|
1629
|
+
"retrofit_generator",
|
|
1630
|
+
"json_serializable",
|
|
1631
|
+
"injectable",
|
|
1632
|
+
"get_it",
|
|
1633
|
+
"go_router",
|
|
1634
|
+
"widgetbook"
|
|
1635
|
+
];
|
|
1636
|
+
function discoverArchitecture(startDir) {
|
|
1637
|
+
const root = findMonorepoRoot(startDir);
|
|
1638
|
+
if (!root) return null;
|
|
1639
|
+
const melosPath = path11.join(root, "melos.yaml");
|
|
1640
|
+
if (!fs12.existsSync(melosPath)) return null;
|
|
1641
|
+
const melosContent = fs12.readFileSync(melosPath, "utf8");
|
|
1642
|
+
const melos = YAML3.parse(melosContent);
|
|
1643
|
+
const projectName = melos?.name ?? path11.basename(root);
|
|
1644
|
+
const apps = [];
|
|
1645
|
+
const appsDir = path11.join(root, "apps");
|
|
1646
|
+
if (fs12.existsSync(appsDir)) {
|
|
1647
|
+
for (const entry of fs12.readdirSync(appsDir, { withFileTypes: true })) {
|
|
1648
|
+
if (!entry.isDirectory()) continue;
|
|
1649
|
+
const candidate = path11.join(appsDir, entry.name);
|
|
1650
|
+
if (fs12.existsSync(path11.join(candidate, "pubspec.yaml"))) {
|
|
1651
|
+
apps.push({ name: entry.name, dir: candidate });
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
const packages = [];
|
|
1656
|
+
const packageBases = ["packages", "packages/features"];
|
|
1657
|
+
for (const base of packageBases) {
|
|
1658
|
+
const baseDir = path11.join(root, base);
|
|
1659
|
+
if (!fs12.existsSync(baseDir)) continue;
|
|
1660
|
+
for (const entry of fs12.readdirSync(baseDir, { withFileTypes: true })) {
|
|
1661
|
+
if (!entry.isDirectory()) continue;
|
|
1662
|
+
const candidate = path11.join(baseDir, entry.name);
|
|
1663
|
+
const pubspecPath = path11.join(candidate, "pubspec.yaml");
|
|
1664
|
+
if (!fs12.existsSync(pubspecPath)) continue;
|
|
1665
|
+
const content = fs12.readFileSync(pubspecPath, "utf8");
|
|
1666
|
+
const pubspec = YAML3.parse(content);
|
|
1667
|
+
const deps = {
|
|
1668
|
+
...pubspec?.dependencies,
|
|
1669
|
+
...pubspec?.dev_dependencies
|
|
1670
|
+
};
|
|
1671
|
+
const keyDeps = KEY_DEPS.filter((d) => d in deps);
|
|
1672
|
+
packages.push({
|
|
1673
|
+
name: pubspec?.name ?? entry.name,
|
|
1674
|
+
dir: candidate,
|
|
1675
|
+
keyDeps
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
let hasDesignSystem = false;
|
|
1680
|
+
let designSystemTiers = [];
|
|
1681
|
+
const ds = detectDesignSystem(root);
|
|
1682
|
+
if (ds) {
|
|
1683
|
+
hasDesignSystem = true;
|
|
1684
|
+
designSystemTiers = ds.availableTiers;
|
|
1685
|
+
}
|
|
1686
|
+
return {
|
|
1687
|
+
root,
|
|
1688
|
+
projectName,
|
|
1689
|
+
apps,
|
|
1690
|
+
packages,
|
|
1691
|
+
hasDesignSystem,
|
|
1692
|
+
designSystemTiers
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
function displayArchitecture(info) {
|
|
1696
|
+
const T = "\u251C\u2500\u2500 ";
|
|
1697
|
+
const L = "\u2514\u2500\u2500 ";
|
|
1698
|
+
const I = "\u2502 ";
|
|
1699
|
+
const S = " ";
|
|
1700
|
+
console.log(chalk7.bold(chalk7.cyan(info.projectName)) + chalk7.gray(` (${info.root})`));
|
|
1701
|
+
console.log(`${I}`);
|
|
1702
|
+
if (info.apps.length > 0) {
|
|
1703
|
+
console.log(chalk7.white(`${T}apps/`));
|
|
1704
|
+
for (let i = 0; i < info.apps.length; i++) {
|
|
1705
|
+
const app = info.apps[i];
|
|
1706
|
+
const prefix = i === info.apps.length - 1 ? `${I}${S}${L}` : `${I}${S}${T}`;
|
|
1707
|
+
console.log(`${prefix}${chalk7.green(app.name)}`);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (info.packages.length > 0) {
|
|
1711
|
+
const rootPkgs = info.packages.filter(
|
|
1712
|
+
(p) => !p.dir.includes(`${path11.sep}features${path11.sep}`)
|
|
1713
|
+
);
|
|
1714
|
+
const featurePkgs = info.packages.filter(
|
|
1715
|
+
(p) => p.dir.includes(`${path11.sep}features${path11.sep}`)
|
|
1716
|
+
);
|
|
1717
|
+
console.log(chalk7.white(`${T}packages/`));
|
|
1718
|
+
for (let i = 0; i < rootPkgs.length; i++) {
|
|
1719
|
+
const pkg2 = rootPkgs[i];
|
|
1720
|
+
const isLast = i === rootPkgs.length - 1 && featurePkgs.length === 0;
|
|
1721
|
+
const prefix = isLast ? `${I}${S}${L}` : `${I}${S}${T}`;
|
|
1722
|
+
const deps = pkg2.keyDeps.length > 0 ? chalk7.dim(` [${pkg2.keyDeps.join(", ")}]`) : "";
|
|
1723
|
+
console.log(`${prefix}${chalk7.yellow(pkg2.name)}${deps}`);
|
|
1724
|
+
}
|
|
1725
|
+
if (featurePkgs.length > 0) {
|
|
1726
|
+
console.log(`${I}${S}${T}features/`);
|
|
1727
|
+
for (let i = 0; i < featurePkgs.length; i++) {
|
|
1728
|
+
const pkg2 = featurePkgs[i];
|
|
1729
|
+
const isLast = i === featurePkgs.length - 1;
|
|
1730
|
+
const prefix = isLast ? `${I}${S}${S}${L}` : `${I}${S}${S}${T}`;
|
|
1731
|
+
const deps = pkg2.keyDeps.length > 0 ? chalk7.dim(` [${pkg2.keyDeps.join(", ")}]`) : "";
|
|
1732
|
+
console.log(`${prefix}${chalk7.yellow(pkg2.name)}${deps}`);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
if (info.hasDesignSystem) {
|
|
1737
|
+
const tiers = info.designSystemTiers.length > 0 ? chalk7.dim(` (${info.designSystemTiers.join(", ")})`) : "";
|
|
1738
|
+
console.log(`${T}${chalk7.magenta("design_system")}${tiers}`);
|
|
1739
|
+
}
|
|
1740
|
+
const bookDir = path11.join(info.root, "book");
|
|
1741
|
+
if (fs12.existsSync(bookDir)) {
|
|
1742
|
+
console.log(`${T}${chalk7.blue("book/")}` + chalk7.dim(" (Docusaurus)"));
|
|
1743
|
+
}
|
|
1744
|
+
if (fs12.existsSync(path11.join(info.root, "melos.yaml"))) {
|
|
1745
|
+
console.log(`${L}${chalk7.gray("melos.yaml")}`);
|
|
1746
|
+
}
|
|
1747
|
+
console.log();
|
|
1748
|
+
}
|
|
1307
1749
|
|
|
1308
1750
|
// src/interactive.ts
|
|
1309
1751
|
var SNAKE_CASE_REGEX4 = /^[a-z][a-z0-9_]*$/;
|
|
@@ -1312,7 +1754,7 @@ async function resolveProject() {
|
|
|
1312
1754
|
s.start("Analyzing current directory...");
|
|
1313
1755
|
const cwdProject = analyzeProject(process.cwd());
|
|
1314
1756
|
if (cwdProject && (cwdProject.hasFreezed || cwdProject.hasBloc)) {
|
|
1315
|
-
s.stop(`Found ${
|
|
1757
|
+
s.stop(`Found ${chalk8.green(cwdProject.projectName)}`);
|
|
1316
1758
|
return cwdProject;
|
|
1317
1759
|
}
|
|
1318
1760
|
s.message("Looking for Melos monorepo...");
|
|
@@ -1325,8 +1767,8 @@ async function resolveProject() {
|
|
|
1325
1767
|
}
|
|
1326
1768
|
}
|
|
1327
1769
|
s.message("Scanning for Flutter projects...");
|
|
1328
|
-
const homeDev =
|
|
1329
|
-
if (
|
|
1770
|
+
const homeDev = path12.join(os.homedir(), "Development");
|
|
1771
|
+
if (fs13.existsSync(homeDev)) {
|
|
1330
1772
|
const projects = discoverProjects(homeDev, 2);
|
|
1331
1773
|
if (projects.length > 0) {
|
|
1332
1774
|
s.stop(`Found ${projects.length} Flutter project(s)`);
|
|
@@ -1334,12 +1776,12 @@ async function resolveProject() {
|
|
|
1334
1776
|
}
|
|
1335
1777
|
}
|
|
1336
1778
|
s.stop("No Flutter projects found");
|
|
1337
|
-
clack.outro(
|
|
1779
|
+
clack.outro(chalk8.red("Could not find any Flutter project with freezed or flutter_bloc."));
|
|
1338
1780
|
return null;
|
|
1339
1781
|
}
|
|
1340
1782
|
async function selectPackage(projects) {
|
|
1341
1783
|
if (projects.length === 1) {
|
|
1342
|
-
clack.log.info(`Using ${
|
|
1784
|
+
clack.log.info(`Using ${chalk8.green(projects[0].projectName)}`);
|
|
1343
1785
|
return projects[0];
|
|
1344
1786
|
}
|
|
1345
1787
|
const selected = await clack.select({
|
|
@@ -1347,7 +1789,7 @@ async function selectPackage(projects) {
|
|
|
1347
1789
|
options: projects.map((p) => ({
|
|
1348
1790
|
value: p,
|
|
1349
1791
|
label: p.projectName,
|
|
1350
|
-
hint:
|
|
1792
|
+
hint: path12.relative(os.homedir(), p.projectRoot)
|
|
1351
1793
|
}))
|
|
1352
1794
|
});
|
|
1353
1795
|
if (clack.isCancel(selected)) {
|
|
@@ -1395,13 +1837,13 @@ async function blocFlow(project) {
|
|
|
1395
1837
|
clack.cancel("Cancelled");
|
|
1396
1838
|
return;
|
|
1397
1839
|
}
|
|
1398
|
-
targetDir =
|
|
1840
|
+
targetDir = path12.resolve(customPath);
|
|
1399
1841
|
} else {
|
|
1400
|
-
targetDir =
|
|
1842
|
+
targetDir = path12.join(project.projectRoot, "lib", "features", feature);
|
|
1401
1843
|
}
|
|
1402
|
-
} else if (
|
|
1403
|
-
targetDir =
|
|
1404
|
-
clack.log.info(`Target: ${
|
|
1844
|
+
} else if (fs13.existsSync(path12.join(project.projectRoot, "lib", "bloc"))) {
|
|
1845
|
+
targetDir = path12.join(project.projectRoot, "lib", "bloc");
|
|
1846
|
+
clack.log.info(`Target: ${chalk8.cyan("lib/bloc/")}`);
|
|
1405
1847
|
} else {
|
|
1406
1848
|
clack.note(
|
|
1407
1849
|
"No lib/features/ directory found. Provide a target path manually.",
|
|
@@ -1418,7 +1860,7 @@ async function blocFlow(project) {
|
|
|
1418
1860
|
clack.cancel("Cancelled");
|
|
1419
1861
|
return;
|
|
1420
1862
|
}
|
|
1421
|
-
targetDir =
|
|
1863
|
+
targetDir = path12.resolve(customPath);
|
|
1422
1864
|
}
|
|
1423
1865
|
const defaultRun = project.hasBuildRunner;
|
|
1424
1866
|
const runBuildRunner2 = await clack.confirm({
|
|
@@ -1437,10 +1879,10 @@ async function blocFlow(project) {
|
|
|
1437
1879
|
buildRunner: runBuildRunner2
|
|
1438
1880
|
});
|
|
1439
1881
|
genSpinner.stop("BLoC generated");
|
|
1440
|
-
clack.outro(
|
|
1882
|
+
clack.outro(chalk8.green("Done!"));
|
|
1441
1883
|
} catch (error) {
|
|
1442
1884
|
genSpinner.stop("Failed");
|
|
1443
|
-
clack.outro(
|
|
1885
|
+
clack.outro(chalk8.red(`Error: ${error}`));
|
|
1444
1886
|
}
|
|
1445
1887
|
}
|
|
1446
1888
|
async function widgetFlow() {
|
|
@@ -1448,14 +1890,14 @@ async function widgetFlow() {
|
|
|
1448
1890
|
const ds = detectDesignSystem(projectRoot);
|
|
1449
1891
|
if (!ds) {
|
|
1450
1892
|
clack.outro(
|
|
1451
|
-
|
|
1893
|
+
chalk8.red(
|
|
1452
1894
|
"No design system detected. Run this command from a project with wl_design_system/ directory."
|
|
1453
1895
|
)
|
|
1454
1896
|
);
|
|
1455
1897
|
return;
|
|
1456
1898
|
}
|
|
1457
1899
|
clack.log.info(
|
|
1458
|
-
`Design system: ${
|
|
1900
|
+
`Design system: ${chalk8.cyan(path12.relative(projectRoot, ds.componentsDir))}`
|
|
1459
1901
|
);
|
|
1460
1902
|
const name = await clack.text({
|
|
1461
1903
|
message: "Widget name (snake_case, without wl_ prefix)",
|
|
@@ -1520,17 +1962,17 @@ async function widgetFlow() {
|
|
|
1520
1962
|
genSpinner.stop(`Use-case skipped: ${e}`);
|
|
1521
1963
|
}
|
|
1522
1964
|
}
|
|
1523
|
-
clack.outro(
|
|
1965
|
+
clack.outro(chalk8.green("Done!"));
|
|
1524
1966
|
} catch (error) {
|
|
1525
1967
|
genSpinner.stop("Failed");
|
|
1526
|
-
clack.outro(
|
|
1968
|
+
clack.outro(chalk8.red(`Error: ${error}`));
|
|
1527
1969
|
}
|
|
1528
1970
|
}
|
|
1529
|
-
async function useCaseFlow(
|
|
1530
|
-
const ds = detectDesignSystem(
|
|
1971
|
+
async function useCaseFlow() {
|
|
1972
|
+
const ds = detectDesignSystem(process.cwd());
|
|
1531
1973
|
if (!ds) {
|
|
1532
1974
|
clack.outro(
|
|
1533
|
-
|
|
1975
|
+
chalk8.red(
|
|
1534
1976
|
"No design system detected. Ensure wl_design_system/ directory exists."
|
|
1535
1977
|
)
|
|
1536
1978
|
);
|
|
@@ -1538,7 +1980,7 @@ async function useCaseFlow(project) {
|
|
|
1538
1980
|
}
|
|
1539
1981
|
if (!ds.widgetbookDir) {
|
|
1540
1982
|
clack.outro(
|
|
1541
|
-
|
|
1983
|
+
chalk8.red(
|
|
1542
1984
|
"No widgetbook package detected. Ensure apps/widgetbook/ exists."
|
|
1543
1985
|
)
|
|
1544
1986
|
);
|
|
@@ -1583,22 +2025,22 @@ async function useCaseFlow(project) {
|
|
|
1583
2025
|
genSpinner.start("Generating use-case file...");
|
|
1584
2026
|
try {
|
|
1585
2027
|
await createUseCase(name, tier, {
|
|
1586
|
-
projectRoot:
|
|
2028
|
+
projectRoot: process.cwd(),
|
|
1587
2029
|
buildRunner: runBuildRunner2
|
|
1588
2030
|
});
|
|
1589
2031
|
genSpinner.stop("Use-case generated");
|
|
1590
|
-
clack.outro(
|
|
2032
|
+
clack.outro(chalk8.green("Done!"));
|
|
1591
2033
|
} catch (error) {
|
|
1592
2034
|
genSpinner.stop("Failed");
|
|
1593
|
-
clack.outro(
|
|
2035
|
+
clack.outro(chalk8.red(`Error: ${error}`));
|
|
1594
2036
|
}
|
|
1595
2037
|
}
|
|
1596
2038
|
function findBffFiles(dir) {
|
|
1597
2039
|
const results = [];
|
|
1598
|
-
if (!
|
|
1599
|
-
const entries =
|
|
2040
|
+
if (!fs13.existsSync(dir)) return results;
|
|
2041
|
+
const entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
1600
2042
|
for (const entry of entries) {
|
|
1601
|
-
const fullPath =
|
|
2043
|
+
const fullPath = path12.join(dir, entry.name);
|
|
1602
2044
|
if (entry.isDirectory()) {
|
|
1603
2045
|
results.push(...findBffFiles(fullPath));
|
|
1604
2046
|
} else if (entry.isFile() && entry.name.endsWith(".dart") && !entry.name.includes(".g.")) {
|
|
@@ -1610,8 +2052,8 @@ function findBffFiles(dir) {
|
|
|
1610
2052
|
function inferBffFile(bffFiles, endpointPath) {
|
|
1611
2053
|
const firstSegment = endpointPath.replace(/^\//, "").split("/")[0].toLowerCase();
|
|
1612
2054
|
if (!firstSegment) return null;
|
|
1613
|
-
const withDomains = bffFiles.map((f) => {
|
|
1614
|
-
const base =
|
|
2055
|
+
const withDomains = bffFiles.filter((f) => /^bff_.+_api\.dart$/.test(path12.basename(f))).map((f) => {
|
|
2056
|
+
const base = path12.basename(f, ".dart");
|
|
1615
2057
|
const domain = base.replace(/^bff_/, "").replace(/_api$/, "");
|
|
1616
2058
|
return { file: f, domain };
|
|
1617
2059
|
});
|
|
@@ -1635,42 +2077,68 @@ async function resolveEndpointProject() {
|
|
|
1635
2077
|
s.start("Finding BFF package...");
|
|
1636
2078
|
const monorepoRoot = findMonorepoRoot(process.cwd());
|
|
1637
2079
|
if (monorepoRoot) {
|
|
2080
|
+
s.message(`Monorepo found at ${monorepoRoot}`);
|
|
1638
2081
|
const packageBases = ["packages", "packages/features"];
|
|
1639
2082
|
for (const base of packageBases) {
|
|
1640
|
-
const baseDir =
|
|
1641
|
-
if (!
|
|
1642
|
-
const entries =
|
|
2083
|
+
const baseDir = path12.join(monorepoRoot, base);
|
|
2084
|
+
if (!fs13.existsSync(baseDir)) continue;
|
|
2085
|
+
const entries = fs13.readdirSync(baseDir, { withFileTypes: true });
|
|
1643
2086
|
for (const entry of entries) {
|
|
1644
2087
|
if (!entry.isDirectory()) continue;
|
|
1645
|
-
const candidate =
|
|
1646
|
-
if (!
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
2088
|
+
const candidate = path12.join(baseDir, entry.name);
|
|
2089
|
+
if (!fs13.existsSync(path12.join(candidate, "pubspec.yaml"))) continue;
|
|
2090
|
+
const bffPath = path12.join(candidate, "lib", "data", "api", "bff");
|
|
2091
|
+
s.message(`Checking ${candidate} \u2192 bff exists: ${fs13.existsSync(bffPath)}`);
|
|
2092
|
+
if (fs13.existsSync(bffPath)) {
|
|
2093
|
+
const project2 = analyzeProject(candidate);
|
|
2094
|
+
if (project2) {
|
|
2095
|
+
s.stop(`Using ${chalk8.green(project2.projectName)}`);
|
|
2096
|
+
return project2;
|
|
1652
2097
|
}
|
|
1653
2098
|
}
|
|
1654
2099
|
}
|
|
1655
2100
|
}
|
|
2101
|
+
} else {
|
|
2102
|
+
s.message("No monorepo root found");
|
|
1656
2103
|
}
|
|
1657
2104
|
const cwdProject = analyzeProject(process.cwd());
|
|
1658
|
-
if (cwdProject &&
|
|
1659
|
-
s.stop(`Using ${
|
|
2105
|
+
if (cwdProject && fs13.existsSync(path12.join(cwdProject.projectRoot, "lib", "data", "api", "bff"))) {
|
|
2106
|
+
s.stop(`Using ${chalk8.green(cwdProject.projectName)}`);
|
|
1660
2107
|
return cwdProject;
|
|
1661
2108
|
}
|
|
1662
2109
|
s.stop("No BFF package found");
|
|
1663
|
-
|
|
1664
|
-
|
|
2110
|
+
const manualPath = await clack.text({
|
|
2111
|
+
message: "Enter the path to the package (must contain lib/data/api/bff/):",
|
|
2112
|
+
placeholder: "e.g. /path/to/my-package or ./packages/core",
|
|
2113
|
+
validate: (v) => {
|
|
2114
|
+
if (!v.trim()) return "Path is required";
|
|
2115
|
+
const resolved2 = path12.resolve(v.trim());
|
|
2116
|
+
if (!fs13.existsSync(resolved2)) return "Path does not exist";
|
|
2117
|
+
if (!fs13.existsSync(path12.join(resolved2, "pubspec.yaml"))) return "No pubspec.yaml found at this path";
|
|
2118
|
+
if (!fs13.existsSync(path12.join(resolved2, "lib", "data", "api", "bff"))) return "No lib/data/api/bff/ found at this path";
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
if (clack.isCancel(manualPath)) {
|
|
2122
|
+
clack.cancel("Cancelled");
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
const resolved = path12.resolve(manualPath);
|
|
2126
|
+
const project = analyzeProject(resolved);
|
|
2127
|
+
if (!project) {
|
|
2128
|
+
clack.outro(chalk8.red(`Could not analyze project at ${resolved}`));
|
|
2129
|
+
return null;
|
|
2130
|
+
}
|
|
2131
|
+
clack.log.info(`Using ${chalk8.green(project.projectName)}`);
|
|
2132
|
+
return project;
|
|
1665
2133
|
}
|
|
1666
2134
|
async function endpointFlow() {
|
|
1667
2135
|
const project = await resolveEndpointProject();
|
|
1668
2136
|
if (!project) return;
|
|
1669
|
-
const lib =
|
|
1670
|
-
const bffDir =
|
|
2137
|
+
const lib = path12.join(project.projectRoot, "lib");
|
|
2138
|
+
const bffDir = path12.join(lib, "data", "api", "bff");
|
|
1671
2139
|
const bffFiles = findBffFiles(bffDir);
|
|
1672
2140
|
if (bffFiles.length === 0) {
|
|
1673
|
-
clack.outro(
|
|
2141
|
+
clack.outro(chalk8.red("No BFF API files found. Ensure lib/data/api/bff/ exists with .dart files."));
|
|
1674
2142
|
return;
|
|
1675
2143
|
}
|
|
1676
2144
|
const httpMethod = await clack.select({
|
|
@@ -1700,13 +2168,13 @@ async function endpointFlow() {
|
|
|
1700
2168
|
}
|
|
1701
2169
|
let bffApiFile = inferBffFile(bffFiles, endpointPath);
|
|
1702
2170
|
if (bffApiFile) {
|
|
1703
|
-
clack.log.info(`Auto-detected BFF file: ${
|
|
2171
|
+
clack.log.info(`Auto-detected BFF file: ${chalk8.cyan(path12.basename(bffApiFile))}`);
|
|
1704
2172
|
} else {
|
|
1705
2173
|
const selected = await clack.select({
|
|
1706
2174
|
message: "Could not auto-detect BFF file. Select one:",
|
|
1707
2175
|
options: bffFiles.map((f) => ({
|
|
1708
2176
|
value: f,
|
|
1709
|
-
label:
|
|
2177
|
+
label: path12.relative(bffDir, f)
|
|
1710
2178
|
}))
|
|
1711
2179
|
});
|
|
1712
2180
|
if (clack.isCancel(selected)) {
|
|
@@ -1731,6 +2199,31 @@ async function endpointFlow() {
|
|
|
1731
2199
|
clack.cancel("Cancelled");
|
|
1732
2200
|
return;
|
|
1733
2201
|
}
|
|
2202
|
+
const diTarget = await clack.select({
|
|
2203
|
+
message: "Where to register DI modules?",
|
|
2204
|
+
options: [
|
|
2205
|
+
{ value: "app_base", label: "app_base" },
|
|
2206
|
+
{ value: "app_base_loyalty", label: "app_base_loyalty" },
|
|
2207
|
+
{ value: "none", label: "Skip (no DI registration)" }
|
|
2208
|
+
],
|
|
2209
|
+
initialValue: "app_base"
|
|
2210
|
+
});
|
|
2211
|
+
if (clack.isCancel(diTarget)) {
|
|
2212
|
+
clack.cancel("Cancelled");
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
let diLazySingleton = true;
|
|
2216
|
+
if (diTarget !== "none") {
|
|
2217
|
+
const lazyAnswer = await clack.confirm({
|
|
2218
|
+
message: "Use @lazySingleton annotation?",
|
|
2219
|
+
initialValue: true
|
|
2220
|
+
});
|
|
2221
|
+
if (clack.isCancel(lazyAnswer)) {
|
|
2222
|
+
clack.cancel("Cancelled");
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
diLazySingleton = lazyAnswer;
|
|
2226
|
+
}
|
|
1734
2227
|
const genSpinner = clack.spinner();
|
|
1735
2228
|
genSpinner.start("Generating endpoint stack...");
|
|
1736
2229
|
try {
|
|
@@ -1740,17 +2233,71 @@ async function endpointFlow() {
|
|
|
1740
2233
|
httpMethod,
|
|
1741
2234
|
endpointPath,
|
|
1742
2235
|
bffApiFile,
|
|
1743
|
-
useCaseName
|
|
2236
|
+
useCaseName,
|
|
2237
|
+
diTarget,
|
|
2238
|
+
diLazySingleton
|
|
1744
2239
|
});
|
|
1745
2240
|
genSpinner.stop("Endpoint generated");
|
|
1746
|
-
clack.outro(
|
|
2241
|
+
clack.outro(chalk8.green("Done!"));
|
|
1747
2242
|
} catch (error) {
|
|
1748
2243
|
genSpinner.stop("Failed");
|
|
1749
|
-
clack.outro(
|
|
2244
|
+
clack.outro(chalk8.red(`Error: ${error}`));
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
async function docsInteractiveMode() {
|
|
2248
|
+
clack.intro(chalk8.bgCyan(chalk8.black(" wlmaker docs ")));
|
|
2249
|
+
const action = await clack.select({
|
|
2250
|
+
message: "What do you want to do?",
|
|
2251
|
+
options: [
|
|
2252
|
+
{ value: "serve", label: "Serve", hint: "Start Docusaurus dev server" },
|
|
2253
|
+
{ value: "commands", label: "Commands", hint: "Show Makefile & melos commands" },
|
|
2254
|
+
{ value: "architecture", label: "Architecture", hint: "Display monorepo tree" }
|
|
2255
|
+
]
|
|
2256
|
+
});
|
|
2257
|
+
if (clack.isCancel(action)) {
|
|
2258
|
+
clack.cancel("Cancelled");
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
switch (action) {
|
|
2262
|
+
case "serve": {
|
|
2263
|
+
const bookDir = detectBookDir(process.cwd());
|
|
2264
|
+
if (!bookDir) {
|
|
2265
|
+
clack.outro(
|
|
2266
|
+
chalk8.red("No Docusaurus book/ directory found. Run from a monorepo root.")
|
|
2267
|
+
);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
clack.log.info(`Serving docs from ${chalk8.cyan(bookDir)}`);
|
|
2271
|
+
await serveBook(bookDir);
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
case "commands": {
|
|
2275
|
+
const commands = discoverCommands(process.cwd());
|
|
2276
|
+
if (commands.length === 0) {
|
|
2277
|
+
clack.outro(chalk8.yellow("No commands found. Run from a monorepo root."));
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
clack.log.info(`Found ${chalk8.green(commands.length.toString())} command(s)`);
|
|
2281
|
+
displayCommands(commands);
|
|
2282
|
+
clack.outro(chalk8.green("Done!"));
|
|
2283
|
+
break;
|
|
2284
|
+
}
|
|
2285
|
+
case "architecture": {
|
|
2286
|
+
const info = discoverArchitecture(process.cwd());
|
|
2287
|
+
if (!info) {
|
|
2288
|
+
clack.outro(
|
|
2289
|
+
chalk8.yellow("No monorepo detected. Run from within a Melos monorepo.")
|
|
2290
|
+
);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
displayArchitecture(info);
|
|
2294
|
+
clack.outro(chalk8.green("Done!"));
|
|
2295
|
+
break;
|
|
2296
|
+
}
|
|
1750
2297
|
}
|
|
1751
2298
|
}
|
|
1752
2299
|
async function interactiveMode() {
|
|
1753
|
-
clack.intro(
|
|
2300
|
+
clack.intro(chalk8.bgCyan(chalk8.black(" wlmaker ")));
|
|
1754
2301
|
const createType = await clack.select({
|
|
1755
2302
|
message: "What do you want to create?",
|
|
1756
2303
|
options: [
|
|
@@ -1765,6 +2312,11 @@ async function interactiveMode() {
|
|
|
1765
2312
|
value: "endpoint",
|
|
1766
2313
|
label: "Endpoint",
|
|
1767
2314
|
hint: "BFF Clean Architecture stack"
|
|
2315
|
+
},
|
|
2316
|
+
{
|
|
2317
|
+
value: "docs",
|
|
2318
|
+
label: "Docs",
|
|
2319
|
+
hint: "Project documentation tools"
|
|
1768
2320
|
}
|
|
1769
2321
|
]
|
|
1770
2322
|
});
|
|
@@ -1783,15 +2335,15 @@ async function interactiveMode() {
|
|
|
1783
2335
|
case "widget":
|
|
1784
2336
|
await widgetFlow();
|
|
1785
2337
|
break;
|
|
1786
|
-
case "usecase":
|
|
1787
|
-
|
|
1788
|
-
if (!project) return;
|
|
1789
|
-
await useCaseFlow(project);
|
|
2338
|
+
case "usecase":
|
|
2339
|
+
await useCaseFlow();
|
|
1790
2340
|
break;
|
|
1791
|
-
}
|
|
1792
2341
|
case "endpoint":
|
|
1793
2342
|
await endpointFlow();
|
|
1794
2343
|
break;
|
|
2344
|
+
case "docs":
|
|
2345
|
+
await docsInteractiveMode();
|
|
2346
|
+
break;
|
|
1795
2347
|
}
|
|
1796
2348
|
}
|
|
1797
2349
|
|
|
@@ -1811,7 +2363,7 @@ program.command("bloc").description("Create a new BLoC with Freezed sealed class
|
|
|
1811
2363
|
try {
|
|
1812
2364
|
await createBloc(name, options);
|
|
1813
2365
|
} catch (error) {
|
|
1814
|
-
console.error(
|
|
2366
|
+
console.error(chalk9.red(`Error: ${error}`));
|
|
1815
2367
|
process.exit(1);
|
|
1816
2368
|
}
|
|
1817
2369
|
}
|
|
@@ -1828,12 +2380,12 @@ program.command("widget").description("Create a new widget in the design system"
|
|
|
1828
2380
|
projectRoot: options.dir,
|
|
1829
2381
|
buildRunner: false
|
|
1830
2382
|
});
|
|
1831
|
-
console.log(
|
|
2383
|
+
console.log(chalk9.green("Widgetbook use-case created"));
|
|
1832
2384
|
} catch {
|
|
1833
|
-
console.log(
|
|
2385
|
+
console.log(chalk9.yellow("Use-case skipped (may already exist)"));
|
|
1834
2386
|
}
|
|
1835
2387
|
} catch (error) {
|
|
1836
|
-
console.error(
|
|
2388
|
+
console.error(chalk9.red(`Error: ${error}`));
|
|
1837
2389
|
process.exit(1);
|
|
1838
2390
|
}
|
|
1839
2391
|
}
|
|
@@ -1846,7 +2398,7 @@ program.command("usecase").description("Create a Widgetbook use-case for an exis
|
|
|
1846
2398
|
buildRunner: options.buildRunner
|
|
1847
2399
|
});
|
|
1848
2400
|
} catch (error) {
|
|
1849
|
-
console.error(
|
|
2401
|
+
console.error(chalk9.red(`Error: ${error}`));
|
|
1850
2402
|
process.exit(1);
|
|
1851
2403
|
}
|
|
1852
2404
|
}
|
|
@@ -1856,6 +2408,37 @@ program.command("endpoint").description("Generate Clean Architecture stack for a
|
|
|
1856
2408
|
if (!project) return;
|
|
1857
2409
|
await endpointFlow(project);
|
|
1858
2410
|
});
|
|
2411
|
+
var docsCmd = program.command("docs").description("Project documentation tools");
|
|
2412
|
+
docsCmd.command("serve").description("Start Docusaurus dev server").option("-d, --dir <path>", "project root directory", process.cwd()).action(async (options) => {
|
|
2413
|
+
const bookDir = detectBookDir(options.dir);
|
|
2414
|
+
if (!bookDir) {
|
|
2415
|
+
console.error(chalk9.red("No Docusaurus book/ directory found. Run from a monorepo root."));
|
|
2416
|
+
process.exit(1);
|
|
2417
|
+
}
|
|
2418
|
+
console.log(chalk9.cyan(`Serving docs from ${bookDir}`));
|
|
2419
|
+
await serveBook(bookDir);
|
|
2420
|
+
});
|
|
2421
|
+
docsCmd.command("commands").description("Show Makefile & melos commands reference").option("-d, --dir <path>", "project root directory", process.cwd()).action(async (options) => {
|
|
2422
|
+
const commands = discoverCommands(options.dir);
|
|
2423
|
+
if (commands.length === 0) {
|
|
2424
|
+
console.log(chalk9.yellow("No commands found. Run from a monorepo root."));
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
console.log(chalk9.green(`Found ${commands.length} command(s)
|
|
2428
|
+
`));
|
|
2429
|
+
displayCommands(commands);
|
|
2430
|
+
});
|
|
2431
|
+
docsCmd.command("architecture").description("Display monorepo architecture tree").option("-d, --dir <path>", "project root directory", process.cwd()).action(async (options) => {
|
|
2432
|
+
const info = discoverArchitecture(options.dir);
|
|
2433
|
+
if (!info) {
|
|
2434
|
+
console.log(chalk9.yellow("No monorepo detected. Run from within a Melos monorepo."));
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
displayArchitecture(info);
|
|
2438
|
+
});
|
|
2439
|
+
docsCmd.action(async () => {
|
|
2440
|
+
await docsInteractiveMode();
|
|
2441
|
+
});
|
|
1859
2442
|
program.action(async () => {
|
|
1860
2443
|
await interactiveMode();
|
|
1861
2444
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wlmaker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Create Flutter BLoCs with Freezed sealed classes from the terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flutter",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"bin": {
|
|
19
|
-
"wlmaker": "./dist/cli.mjs"
|
|
19
|
+
"wlmaker": "./dist/cli.mjs",
|
|
20
|
+
"wl": "./dist/cli.mjs"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist"
|