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.
Files changed (2) hide show
  1. package/dist/cli.mjs +160 -13
  2. 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((resolve2) => {
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
- resolve2();
101
+ resolve3();
101
102
  });
102
103
  child.on("error", () => {
103
- resolve2();
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
- console.error(chalk.red("Error: Name cannot be empty."));
114
- process.exit(1);
114
+ throw new Error("Name cannot be empty.");
115
115
  }
116
116
  if (!SNAKE_CASE_REGEX.test(name)) {
117
- console.error(chalk.red("Error: Name must be snake_case (lowercase letters, digits, underscores)."));
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
- console.error(chalk.red(`Error: Directory "${name}" already exists.`));
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
- console.error(chalk.red(`Failed to create BLoC: ${error}`));
145
- process.exit(1);
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("<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) => {
153
- await createBloc(name, options);
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.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",