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