wlmaker 1.0.0 → 1.0.1
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 +160 -13
- package/package.json +4 -2
package/dist/cli.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import chalk3 from "chalk";
|
|
5
6
|
|
|
6
7
|
// src/core/create-bloc.ts
|
|
7
8
|
import * as fs3 from "fs";
|
|
@@ -90,17 +91,17 @@ function hasBuildRunner(projectRoot) {
|
|
|
90
91
|
return /build_runner/.test(pubspec);
|
|
91
92
|
}
|
|
92
93
|
function runBuildRunner(projectRoot) {
|
|
93
|
-
return new Promise((
|
|
94
|
+
return new Promise((resolve3) => {
|
|
94
95
|
const child = spawn(
|
|
95
96
|
"dart",
|
|
96
97
|
["run", "build_runner", "build", "--delete-conflicting-outputs"],
|
|
97
98
|
{ cwd: projectRoot, stdio: "inherit" }
|
|
98
99
|
);
|
|
99
100
|
child.on("close", (code) => {
|
|
100
|
-
|
|
101
|
+
resolve3();
|
|
101
102
|
});
|
|
102
103
|
child.on("error", () => {
|
|
103
|
-
|
|
104
|
+
resolve3();
|
|
104
105
|
});
|
|
105
106
|
});
|
|
106
107
|
}
|
|
@@ -110,16 +111,13 @@ var SNAKE_CASE_REGEX = /^[a-z][a-z0-9_]*$/;
|
|
|
110
111
|
async function createBloc(name, options) {
|
|
111
112
|
const targetDir = path3.resolve(options.dir);
|
|
112
113
|
if (!name || name.trim().length === 0) {
|
|
113
|
-
|
|
114
|
-
process.exit(1);
|
|
114
|
+
throw new Error("Name cannot be empty.");
|
|
115
115
|
}
|
|
116
116
|
if (!SNAKE_CASE_REGEX.test(name)) {
|
|
117
|
-
|
|
118
|
-
process.exit(1);
|
|
117
|
+
throw new Error("Name must be snake_case (lowercase letters, digits, underscores).");
|
|
119
118
|
}
|
|
120
119
|
if (fs3.existsSync(path3.join(targetDir, name))) {
|
|
121
|
-
|
|
122
|
-
process.exit(1);
|
|
120
|
+
throw new Error(`Directory "${name}" already exists.`);
|
|
123
121
|
}
|
|
124
122
|
const pascal = pascalCase(name);
|
|
125
123
|
const blocDir = path3.join(targetDir, name);
|
|
@@ -141,15 +139,164 @@ async function createBloc(name, options) {
|
|
|
141
139
|
}
|
|
142
140
|
}
|
|
143
141
|
} catch (error) {
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
throw new Error(`Failed to create BLoC: ${error}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/interactive.ts
|
|
147
|
+
import * as path5 from "path";
|
|
148
|
+
import * as clack from "@clack/prompts";
|
|
149
|
+
import chalk2 from "chalk";
|
|
150
|
+
|
|
151
|
+
// src/core/project-analyzer.ts
|
|
152
|
+
import * as fs4 from "fs";
|
|
153
|
+
import * as path4 from "path";
|
|
154
|
+
import YAML from "yaml";
|
|
155
|
+
function analyzeProject(startDir) {
|
|
156
|
+
const projectRoot = findPubspecDir2(startDir);
|
|
157
|
+
if (!projectRoot) return null;
|
|
158
|
+
const pubspecPath = path4.join(projectRoot, "pubspec.yaml");
|
|
159
|
+
const content = fs4.readFileSync(pubspecPath, "utf8");
|
|
160
|
+
const pubspec = YAML.parse(content);
|
|
161
|
+
const deps = {
|
|
162
|
+
...pubspec?.dependencies,
|
|
163
|
+
...pubspec?.dev_dependencies
|
|
164
|
+
};
|
|
165
|
+
const features = discoverFeatures(projectRoot);
|
|
166
|
+
return {
|
|
167
|
+
projectRoot,
|
|
168
|
+
projectName: pubspec?.name ?? path4.basename(projectRoot),
|
|
169
|
+
hasFreezed: "freezed" in deps || "freezed_annotation" in deps,
|
|
170
|
+
hasBloc: "flutter_bloc" in deps || "bloc" in deps,
|
|
171
|
+
hasBuildRunner: "build_runner" in deps,
|
|
172
|
+
features
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function findPubspecDir2(startDir) {
|
|
176
|
+
let dir = startDir;
|
|
177
|
+
while (dir !== path4.dirname(dir)) {
|
|
178
|
+
if (fs4.existsSync(path4.join(dir, "pubspec.yaml"))) {
|
|
179
|
+
return dir;
|
|
180
|
+
}
|
|
181
|
+
dir = path4.dirname(dir);
|
|
182
|
+
}
|
|
183
|
+
return void 0;
|
|
184
|
+
}
|
|
185
|
+
function discoverFeatures(projectRoot) {
|
|
186
|
+
const featuresDir = path4.join(projectRoot, "lib", "features");
|
|
187
|
+
if (!fs4.existsSync(featuresDir)) return [];
|
|
188
|
+
return fs4.readdirSync(featuresDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/interactive.ts
|
|
192
|
+
var SNAKE_CASE_REGEX2 = /^[a-z][a-z0-9_]*$/;
|
|
193
|
+
async function interactiveMode() {
|
|
194
|
+
clack.intro(chalk2.bgCyan(chalk2.black(" wlmaker ")));
|
|
195
|
+
const s = clack.spinner();
|
|
196
|
+
s.start("Analyzing Flutter project...");
|
|
197
|
+
const project = analyzeProject(process.cwd());
|
|
198
|
+
if (!project) {
|
|
199
|
+
s.stop("No Flutter project found");
|
|
200
|
+
clack.outro(chalk2.red("Could not find a pubspec.yaml in the current directory or any parent."));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
s.stop(`Found ${chalk2.green(project.projectName)} \u2014 ${project.features.length} feature(s) detected`);
|
|
204
|
+
const name = await clack.text({
|
|
205
|
+
message: "BLoC name (snake_case)",
|
|
206
|
+
placeholder: "e.g. user_login",
|
|
207
|
+
validate: (value) => {
|
|
208
|
+
if (!value.trim()) return "Name is required";
|
|
209
|
+
if (!SNAKE_CASE_REGEX2.test(value)) return "Must be snake_case (lowercase, digits, underscores)";
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
if (clack.isCancel(name)) {
|
|
213
|
+
clack.cancel("Cancelled");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
let targetDir;
|
|
217
|
+
if (project.features.length > 0) {
|
|
218
|
+
const feature = await clack.select({
|
|
219
|
+
message: "Select feature",
|
|
220
|
+
options: [
|
|
221
|
+
...project.features.map((f) => ({ value: f, label: f })),
|
|
222
|
+
{ value: "__custom__", label: "Custom path..." }
|
|
223
|
+
]
|
|
224
|
+
});
|
|
225
|
+
if (clack.isCancel(feature)) {
|
|
226
|
+
clack.cancel("Cancelled");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (feature === "__custom__") {
|
|
230
|
+
const customPath = await clack.text({
|
|
231
|
+
message: "Target directory path",
|
|
232
|
+
placeholder: "lib/features/auth",
|
|
233
|
+
validate: (v) => {
|
|
234
|
+
if (!v.trim()) return "Path is required";
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
if (clack.isCancel(customPath)) {
|
|
238
|
+
clack.cancel("Cancelled");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
targetDir = path5.resolve(customPath);
|
|
242
|
+
} else {
|
|
243
|
+
targetDir = path5.join(project.projectRoot, "lib", "features", feature);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
clack.note("No lib/features/ directory found. Provide a target path manually.", "Info");
|
|
247
|
+
const customPath = await clack.text({
|
|
248
|
+
message: "Target directory path",
|
|
249
|
+
placeholder: "lib/features/auth",
|
|
250
|
+
validate: (v) => {
|
|
251
|
+
if (!v.trim()) return "Path is required";
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
if (clack.isCancel(customPath)) {
|
|
255
|
+
clack.cancel("Cancelled");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
targetDir = path5.resolve(customPath);
|
|
259
|
+
}
|
|
260
|
+
const defaultRun = project.hasBuildRunner;
|
|
261
|
+
const runBuildRunner2 = await clack.confirm({
|
|
262
|
+
message: "Run build_runner after generation?",
|
|
263
|
+
initialValue: defaultRun
|
|
264
|
+
});
|
|
265
|
+
if (clack.isCancel(runBuildRunner2)) {
|
|
266
|
+
clack.cancel("Cancelled");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const genSpinner = clack.spinner();
|
|
270
|
+
genSpinner.start("Generating BLoC files...");
|
|
271
|
+
try {
|
|
272
|
+
await createBloc(name, {
|
|
273
|
+
dir: targetDir,
|
|
274
|
+
buildRunner: runBuildRunner2
|
|
275
|
+
});
|
|
276
|
+
genSpinner.stop("BLoC generated");
|
|
277
|
+
clack.outro(chalk2.green("Done!"));
|
|
278
|
+
} catch (error) {
|
|
279
|
+
genSpinner.stop("Failed");
|
|
280
|
+
clack.outro(chalk2.red(`Error: ${error}`));
|
|
146
281
|
}
|
|
147
282
|
}
|
|
148
283
|
|
|
149
284
|
// src/cli.ts
|
|
150
285
|
var program = new Command();
|
|
151
286
|
program.name("wlmaker").description("Create Flutter BLoCs with Freezed sealed classes from the terminal").version("1.0.0");
|
|
152
|
-
program.command("bloc").description("Create a new BLoC with Freezed sealed classes").argument("
|
|
153
|
-
|
|
287
|
+
program.command("bloc").description("Create a new BLoC with Freezed sealed classes").argument("[name]", "BLoC name in snake_case (e.g. user_login)").option("-d, --dir <path>", "target directory", process.cwd()).option("--no-build-runner", "skip build_runner execution").action(async (name, options) => {
|
|
288
|
+
if (!name) {
|
|
289
|
+
await interactiveMode();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
await createBloc(name, options);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(chalk3.red(`Error: ${error}`));
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
program.action(async () => {
|
|
300
|
+
await interactiveMode();
|
|
154
301
|
});
|
|
155
302
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wlmaker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Create Flutter BLoCs with Freezed sealed classes from the terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flutter",
|
|
@@ -26,9 +26,11 @@
|
|
|
26
26
|
"dev": "tsup --watch"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@clack/prompts": "^1.2.0",
|
|
29
30
|
"chalk": "^5.3.0",
|
|
30
31
|
"change-case": "^5.4.0",
|
|
31
|
-
"commander": "^12.0.0"
|
|
32
|
+
"commander": "^12.0.0",
|
|
33
|
+
"yaml": "^2.8.3"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
36
|
"@types/node": "^22.0.0",
|