wlmaker 1.1.2 → 1.1.3
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 +1109 -37
- 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 chalk5 from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/core/create-bloc.ts
|
|
9
9
|
import * as fs3 from "fs";
|
|
@@ -72,6 +72,20 @@ function updateBarrelFile(parentDir, name) {
|
|
|
72
72
|
fs.writeFileSync(barrelPath, exportLine + "\n");
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
+
function updateSortedBarrelFile(tierDir, barrelFileName, exportLine) {
|
|
76
|
+
const barrelPath = path.join(tierDir, barrelFileName);
|
|
77
|
+
let lines = [];
|
|
78
|
+
if (fs.existsSync(barrelPath)) {
|
|
79
|
+
const content = fs.readFileSync(barrelPath, "utf8");
|
|
80
|
+
lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
81
|
+
}
|
|
82
|
+
if (lines.includes(exportLine)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
lines.push(exportLine);
|
|
86
|
+
lines.sort();
|
|
87
|
+
fs.writeFileSync(barrelPath, lines.join("\n") + "\n");
|
|
88
|
+
}
|
|
75
89
|
|
|
76
90
|
// src/core/build-runner.ts
|
|
77
91
|
import { spawn } from "child_process";
|
|
@@ -144,12 +158,70 @@ async function createBloc(name, options) {
|
|
|
144
158
|
}
|
|
145
159
|
}
|
|
146
160
|
|
|
147
|
-
// src/
|
|
161
|
+
// src/core/create-widget.ts
|
|
162
|
+
import * as fs6 from "fs";
|
|
163
|
+
import * as path6 from "path";
|
|
164
|
+
import chalk2 from "chalk";
|
|
165
|
+
import { pascalCase as pascalCase2 } from "change-case";
|
|
166
|
+
|
|
167
|
+
// src/core/tier.ts
|
|
168
|
+
var VALID_TIERS = ["atom", "molecule", "organism", "template"];
|
|
169
|
+
var TIER_PATTERNS = {
|
|
170
|
+
atom: ["single-public", "single-factory", "subdirectory-parts"],
|
|
171
|
+
molecule: ["single", "subdirectory-parts", "subdirectory-widgets"],
|
|
172
|
+
organism: ["subdirectory-show", "single"],
|
|
173
|
+
template: ["simple", "config-data-callbacks"]
|
|
174
|
+
};
|
|
175
|
+
var TIER_PLURAL = {
|
|
176
|
+
atom: "atoms",
|
|
177
|
+
molecule: "molecules",
|
|
178
|
+
organism: "organisms",
|
|
179
|
+
template: "templates"
|
|
180
|
+
};
|
|
181
|
+
var TIER_LABELS = {
|
|
182
|
+
atom: "Atom",
|
|
183
|
+
molecule: "Molecule",
|
|
184
|
+
organism: "Organism",
|
|
185
|
+
template: "Template"
|
|
186
|
+
};
|
|
187
|
+
var PATTERN_LABELS = {
|
|
188
|
+
"single-public": "Single file, public constructor",
|
|
189
|
+
"single-factory": "Single file, private constructor + factories",
|
|
190
|
+
"subdirectory-parts": "Subdirectory with part files",
|
|
191
|
+
single: "Single file (StatefulWidget)",
|
|
192
|
+
"subdirectory-widgets": "Subdirectory with widgets/ and utils/",
|
|
193
|
+
"subdirectory-show": "Subdirectory with static show() + parts",
|
|
194
|
+
simple: "Template simple (i18n + body + skeleton)",
|
|
195
|
+
"config-data-callbacks": "Template with Config/Data/Callbacks"
|
|
196
|
+
};
|
|
197
|
+
function normalizeTier(input) {
|
|
198
|
+
const normalized = input.toLowerCase().replace(/s$/, "");
|
|
199
|
+
if (VALID_TIERS.includes(normalized)) {
|
|
200
|
+
return normalized;
|
|
201
|
+
}
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Invalid tier "${input}". Valid tiers: ${VALID_TIERS.join(", ")}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
function tierPlural(tier) {
|
|
207
|
+
return TIER_PLURAL[tier];
|
|
208
|
+
}
|
|
209
|
+
function tierLabel(tier) {
|
|
210
|
+
return TIER_LABELS[tier];
|
|
211
|
+
}
|
|
212
|
+
function getValidPatterns(tier) {
|
|
213
|
+
return TIER_PATTERNS[tier];
|
|
214
|
+
}
|
|
215
|
+
function getDefaultPattern(tier) {
|
|
216
|
+
return TIER_PATTERNS[tier][0];
|
|
217
|
+
}
|
|
218
|
+
function patternLabel(pattern) {
|
|
219
|
+
return PATTERN_LABELS[pattern] ?? pattern;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/core/design-system-analyzer.ts
|
|
148
223
|
import * as fs5 from "fs";
|
|
149
|
-
import * as os from "os";
|
|
150
224
|
import * as path5 from "path";
|
|
151
|
-
import * as clack from "@clack/prompts";
|
|
152
|
-
import chalk2 from "chalk";
|
|
153
225
|
|
|
154
226
|
// src/core/project-analyzer.ts
|
|
155
227
|
import * as fs4 from "fs";
|
|
@@ -253,14 +325,813 @@ function discoverProjects(searchDir, maxDepth = 2) {
|
|
|
253
325
|
return projects.sort((a, b) => a.projectName.localeCompare(b.projectName));
|
|
254
326
|
}
|
|
255
327
|
|
|
256
|
-
// src/
|
|
328
|
+
// src/core/design-system-analyzer.ts
|
|
329
|
+
function detectDesignSystem(projectRoot) {
|
|
330
|
+
const monorepoRoot = findMonorepoRoot(projectRoot);
|
|
331
|
+
if (monorepoRoot) {
|
|
332
|
+
const dsDir = path5.join(
|
|
333
|
+
monorepoRoot,
|
|
334
|
+
"packages",
|
|
335
|
+
"design_system",
|
|
336
|
+
"lib",
|
|
337
|
+
"wl_design_system"
|
|
338
|
+
);
|
|
339
|
+
if (fs5.existsSync(dsDir)) {
|
|
340
|
+
const widgetbookDir = path5.join(monorepoRoot, "apps", "widgetbook");
|
|
341
|
+
return {
|
|
342
|
+
componentsDir: dsDir,
|
|
343
|
+
widgetbookDir: fs5.existsSync(widgetbookDir) ? widgetbookDir : void 0,
|
|
344
|
+
availableTiers: discoverTiers(dsDir)
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const standaloneDir = path5.join(projectRoot, "lib", "wl_design_system");
|
|
349
|
+
if (fs5.existsSync(standaloneDir)) {
|
|
350
|
+
const widgetbookDir = path5.join(
|
|
351
|
+
path5.dirname(projectRoot),
|
|
352
|
+
"widgetbook"
|
|
353
|
+
);
|
|
354
|
+
return {
|
|
355
|
+
componentsDir: standaloneDir,
|
|
356
|
+
widgetbookDir: fs5.existsSync(widgetbookDir) ? widgetbookDir : void 0,
|
|
357
|
+
availableTiers: discoverTiers(standaloneDir)
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
function discoverTiers(componentsDir) {
|
|
363
|
+
const tiers = [];
|
|
364
|
+
const tierSet = new Set(VALID_TIERS.map((t) => tierPlural(t)));
|
|
365
|
+
try {
|
|
366
|
+
const entries = fs5.readdirSync(componentsDir, { withFileTypes: true });
|
|
367
|
+
for (const entry of entries) {
|
|
368
|
+
if (entry.isDirectory() && tierSet.has(entry.name)) {
|
|
369
|
+
tiers.push(entry.name);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
return tiers;
|
|
375
|
+
}
|
|
376
|
+
function widgetExists(componentsDir, tierPluralName, widgetName) {
|
|
377
|
+
const tierDir = path5.join(componentsDir, tierPluralName);
|
|
378
|
+
const flatFile = path5.join(tierDir, `${widgetName}.dart`);
|
|
379
|
+
if (fs5.existsSync(flatFile)) return true;
|
|
380
|
+
const subDir = path5.join(tierDir, widgetName);
|
|
381
|
+
if (fs5.existsSync(subDir)) return true;
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/core/widget-templates.ts
|
|
386
|
+
function atomTemplateA(name, pascal) {
|
|
387
|
+
return `import 'package:flutter/material.dart';
|
|
388
|
+
|
|
389
|
+
class Wl${pascal} extends StatelessWidget {
|
|
390
|
+
const Wl${pascal}({super.key});
|
|
391
|
+
|
|
392
|
+
@override
|
|
393
|
+
Widget build(BuildContext context) {
|
|
394
|
+
final theme = context.theme;
|
|
395
|
+
return const SizedBox.shrink();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
400
|
+
function atomTemplateB(name, pascal) {
|
|
401
|
+
return `import 'package:flutter/material.dart';
|
|
402
|
+
|
|
403
|
+
enum Wl${pascal}Variant {
|
|
404
|
+
primary,
|
|
405
|
+
secondary,
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
class Wl${pascal} extends StatelessWidget {
|
|
409
|
+
final Wl${pascal}Variant variant;
|
|
410
|
+
|
|
411
|
+
const Wl${pascal}._internal({required this.variant, super.key});
|
|
412
|
+
|
|
413
|
+
factory Wl${pascal}.primary({Key? key}) =>
|
|
414
|
+
Wl${pascal}._internal(variant: Wl${pascal}Variant.primary, key: key);
|
|
415
|
+
|
|
416
|
+
factory Wl${pascal}.secondary({Key? key}) =>
|
|
417
|
+
Wl${pascal}._internal(variant: Wl${pascal}Variant.secondary, key: key);
|
|
418
|
+
|
|
419
|
+
@override
|
|
420
|
+
Widget build(BuildContext context) {
|
|
421
|
+
final theme = context.theme;
|
|
422
|
+
return const SizedBox.shrink();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
function atomTemplateC(name, pascal) {
|
|
428
|
+
return {
|
|
429
|
+
main: `import 'package:flutter/material.dart';
|
|
430
|
+
|
|
431
|
+
part '${name}_sizes.dart';
|
|
432
|
+
|
|
433
|
+
class Wl${pascal} extends StatelessWidget {
|
|
434
|
+
final Wl${pascal}Size size;
|
|
435
|
+
|
|
436
|
+
const Wl${pascal}({this.size = Wl${pascal}Size.md, super.key});
|
|
437
|
+
|
|
438
|
+
@override
|
|
439
|
+
Widget build(BuildContext context) {
|
|
440
|
+
final theme = context.theme;
|
|
441
|
+
return SizedBox(
|
|
442
|
+
width: size.width,
|
|
443
|
+
height: size.height,
|
|
444
|
+
child: const SizedBox.shrink(),
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
`,
|
|
449
|
+
sizes: `part of '${name}.dart';
|
|
450
|
+
|
|
451
|
+
enum _Wl${pascal}Size {
|
|
452
|
+
sm,
|
|
453
|
+
md,
|
|
454
|
+
lg,
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
extension _Wl${pascal}SizeExt on _Wl${pascal}Size {
|
|
458
|
+
double get width => switch (this) {
|
|
459
|
+
_Wl${pascal}Size.sm => 16,
|
|
460
|
+
_Wl${pascal}Size.md => 24,
|
|
461
|
+
_Wl${pascal}Size.lg => 32,
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
double get height => switch (this) {
|
|
465
|
+
_Wl${pascal}Size.sm => 16,
|
|
466
|
+
_Wl${pascal}Size.md => 24,
|
|
467
|
+
_Wl${pascal}Size.lg => 32,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
`
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function moleculeTemplateA(name, pascal) {
|
|
474
|
+
return `import 'package:flutter/material.dart';
|
|
475
|
+
|
|
476
|
+
enum Wl${pascal}Variant {
|
|
477
|
+
primary,
|
|
478
|
+
secondary,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
class Wl${pascal} extends StatefulWidget {
|
|
482
|
+
final Wl${pascal}Variant variant;
|
|
483
|
+
|
|
484
|
+
const Wl${pascal}({
|
|
485
|
+
this.variant = Wl${pascal}Variant.primary,
|
|
486
|
+
super.key,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
@override
|
|
490
|
+
State<Wl${pascal}> createState() => _Wl${pascal}State();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
class _Wl${pascal}State extends State<Wl${pascal}> {
|
|
494
|
+
@override
|
|
495
|
+
Widget build(BuildContext context) {
|
|
496
|
+
final theme = context.theme;
|
|
497
|
+
return const SizedBox.shrink();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
`;
|
|
501
|
+
}
|
|
502
|
+
function moleculeTemplateB(name, pascal) {
|
|
503
|
+
return {
|
|
504
|
+
main: `import 'package:flutter/material.dart';
|
|
505
|
+
|
|
506
|
+
part '${name}_type.dart';
|
|
507
|
+
|
|
508
|
+
class Wl${pascal} extends StatefulWidget {
|
|
509
|
+
final Wl${pascal}Variant variant;
|
|
510
|
+
|
|
511
|
+
const Wl${pascal}({
|
|
512
|
+
this.variant = Wl${pascal}Variant.primary,
|
|
513
|
+
super.key,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
@override
|
|
517
|
+
State<Wl${pascal}> createState() => _Wl${pascal}State();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
class _Wl${pascal}State extends State<Wl${pascal}> {
|
|
521
|
+
@override
|
|
522
|
+
Widget build(BuildContext context) {
|
|
523
|
+
final theme = context.theme;
|
|
524
|
+
return const SizedBox.shrink();
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
`,
|
|
528
|
+
type: `part of '${name}.dart';
|
|
529
|
+
|
|
530
|
+
enum Wl${pascal}Variant {
|
|
531
|
+
primary,
|
|
532
|
+
secondary,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
extension Wl${pascal}VariantExt on Wl${pascal}Variant {
|
|
536
|
+
bool get isPrimary => this == Wl${pascal}Variant.primary;
|
|
537
|
+
}
|
|
538
|
+
`
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function moleculeTemplateC(name, pascal) {
|
|
542
|
+
return `import 'package:flutter/material.dart';
|
|
543
|
+
|
|
544
|
+
class Wl${pascal} extends StatefulWidget {
|
|
545
|
+
const Wl${pascal}({super.key});
|
|
546
|
+
|
|
547
|
+
@override
|
|
548
|
+
State<Wl${pascal}> createState() => _Wl${pascal}State();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
class _Wl${pascal}State extends State<Wl${pascal}> {
|
|
552
|
+
@override
|
|
553
|
+
Widget build(BuildContext context) {
|
|
554
|
+
final theme = context.theme;
|
|
555
|
+
return const SizedBox.shrink();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
`;
|
|
559
|
+
}
|
|
560
|
+
function organismTemplateA(name, pascal) {
|
|
561
|
+
return {
|
|
562
|
+
main: `import 'package:flutter/material.dart';
|
|
563
|
+
|
|
564
|
+
part '${name}_content.dart';
|
|
565
|
+
|
|
566
|
+
class Wl${pascal} {
|
|
567
|
+
Wl${pascal}._();
|
|
568
|
+
|
|
569
|
+
static Future<T?> show<T>({
|
|
570
|
+
required BuildContext context,
|
|
571
|
+
}) {
|
|
572
|
+
return showDialog<T>(
|
|
573
|
+
context: context,
|
|
574
|
+
builder: (context) => _Wl${pascal}Content(),
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
`,
|
|
579
|
+
content: `part of '${name}.dart';
|
|
580
|
+
|
|
581
|
+
class _Wl${pascal}Content extends StatelessWidget {
|
|
582
|
+
@override
|
|
583
|
+
Widget build(BuildContext context) {
|
|
584
|
+
final theme = context.theme;
|
|
585
|
+
return const SizedBox.shrink();
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
`
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function organismTemplateB(name, pascal) {
|
|
592
|
+
return `import 'package:flutter/material.dart';
|
|
593
|
+
|
|
594
|
+
class Wl${pascal} extends StatefulWidget {
|
|
595
|
+
const Wl${pascal}({super.key});
|
|
596
|
+
|
|
597
|
+
@override
|
|
598
|
+
State<Wl${pascal}> createState() => _Wl${pascal}State();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
class _Wl${pascal}State extends State<Wl${pascal}> {
|
|
602
|
+
@override
|
|
603
|
+
Widget build(BuildContext context) {
|
|
604
|
+
final theme = context.theme;
|
|
605
|
+
return const SizedBox.shrink();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
`;
|
|
609
|
+
}
|
|
610
|
+
function templateA(name, pascal) {
|
|
611
|
+
return {
|
|
612
|
+
main: `import 'package:flutter/material.dart';
|
|
613
|
+
|
|
614
|
+
part '${name}_i18n.dart';
|
|
615
|
+
part '${name}_body.dart';
|
|
616
|
+
part '${name}_skeleton.dart';
|
|
617
|
+
|
|
618
|
+
class Wl${pascal}Template extends StatefulWidget {
|
|
619
|
+
const Wl${pascal}Template({super.key});
|
|
620
|
+
|
|
621
|
+
@override
|
|
622
|
+
State<Wl${pascal}Template> createState() => _Wl${pascal}TemplateState();
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
class _Wl${pascal}TemplateState extends State<Wl${pascal}Template> {
|
|
626
|
+
bool _isLoading = true;
|
|
627
|
+
|
|
628
|
+
@override
|
|
629
|
+
void initState() {
|
|
630
|
+
super.initState();
|
|
631
|
+
_loadData();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
Future<void> _loadData() async {
|
|
635
|
+
// TODO: Load data
|
|
636
|
+
setState(() => _isLoading = false);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@override
|
|
640
|
+
Widget build(BuildContext context) {
|
|
641
|
+
return Scaffold(
|
|
642
|
+
appBar: AppBar(title: Text(Wl${pascal}I18n.title)),
|
|
643
|
+
body: AnimatedSwitcher(
|
|
644
|
+
duration: const Duration(milliseconds: 300),
|
|
645
|
+
child: _isLoading
|
|
646
|
+
? const _Wl${pascal}Skeleton()
|
|
647
|
+
: const _Wl${pascal}Body(),
|
|
648
|
+
),
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
`,
|
|
653
|
+
i18n: `part of '${name}_template.dart';
|
|
654
|
+
|
|
655
|
+
class Wl${pascal}I18n extends Equatable {
|
|
656
|
+
final String title;
|
|
657
|
+
|
|
658
|
+
const Wl${pascal}I18n({
|
|
659
|
+
this.title = '',
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
static const empty = Wl${pascal}I18n();
|
|
663
|
+
|
|
664
|
+
@override
|
|
665
|
+
List<Object?> get props => [title];
|
|
666
|
+
}
|
|
667
|
+
`,
|
|
668
|
+
body: `part of '${name}_template.dart';
|
|
669
|
+
|
|
670
|
+
class _Wl${pascal}Body extends StatelessWidget {
|
|
671
|
+
const _Wl${pascal}Body();
|
|
672
|
+
|
|
673
|
+
@override
|
|
674
|
+
Widget build(BuildContext context) {
|
|
675
|
+
final theme = context.theme;
|
|
676
|
+
return const SizedBox.shrink();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
`,
|
|
680
|
+
skeleton: `part of '${name}_template.dart';
|
|
681
|
+
|
|
682
|
+
class _Wl${pascal}Skeleton extends StatelessWidget {
|
|
683
|
+
const _Wl${pascal}Skeleton();
|
|
684
|
+
|
|
685
|
+
@override
|
|
686
|
+
Widget build(BuildContext context) {
|
|
687
|
+
return const Center(
|
|
688
|
+
child: CircularProgressIndicator(),
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
`
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function templateB(name, pascal) {
|
|
696
|
+
return {
|
|
697
|
+
main: `import 'package:flutter/material.dart';
|
|
698
|
+
|
|
699
|
+
import 'contracts/${name}_callbacks.dart';
|
|
700
|
+
import 'contracts/${name}_config.dart';
|
|
701
|
+
import 'contracts/${name}_data.dart';
|
|
702
|
+
|
|
703
|
+
part '${name}_i18n.dart';
|
|
704
|
+
part '${name}_body.dart';
|
|
705
|
+
part '${name}_skeleton.dart';
|
|
706
|
+
|
|
707
|
+
class Wl${pascal}Template extends StatefulWidget {
|
|
708
|
+
final Wl${pascal}Config config;
|
|
709
|
+
final Wl${pascal}Callbacks callbacks;
|
|
710
|
+
|
|
711
|
+
const Wl${pascal}Template({
|
|
712
|
+
required this.config,
|
|
713
|
+
required this.callbacks,
|
|
714
|
+
super.key,
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
@override
|
|
718
|
+
State<Wl${pascal}Template> createState() => _Wl${pascal}TemplateState();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
class _Wl${pascal}TemplateState extends State<Wl${pascal}Template> {
|
|
722
|
+
Wl${pascal}Data _data = const Wl${pascal}Data();
|
|
723
|
+
bool _isLoading = true;
|
|
724
|
+
|
|
725
|
+
@override
|
|
726
|
+
void initState() {
|
|
727
|
+
super.initState();
|
|
728
|
+
_loadData();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
Future<void> _loadData() async {
|
|
732
|
+
// TODO: Load data
|
|
733
|
+
setState(() => _isLoading = false);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@override
|
|
737
|
+
Widget build(BuildContext context) {
|
|
738
|
+
return Scaffold(
|
|
739
|
+
appBar: AppBar(title: Text(widget.config.i18n.title)),
|
|
740
|
+
body: AnimatedSwitcher(
|
|
741
|
+
duration: const Duration(milliseconds: 300),
|
|
742
|
+
child: _isLoading
|
|
743
|
+
? const _Wl${pascal}Skeleton()
|
|
744
|
+
: _Wl${pascal}Body(
|
|
745
|
+
data: _data,
|
|
746
|
+
callbacks: widget.callbacks,
|
|
747
|
+
),
|
|
748
|
+
),
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
`,
|
|
753
|
+
i18n: `part of '${name}_template.dart';
|
|
754
|
+
|
|
755
|
+
class Wl${pascal}I18n extends Equatable {
|
|
756
|
+
final String title;
|
|
757
|
+
|
|
758
|
+
const Wl${pascal}I18n({
|
|
759
|
+
this.title = '',
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
static const empty = Wl${pascal}I18n();
|
|
763
|
+
|
|
764
|
+
@override
|
|
765
|
+
List<Object?> get props => [title];
|
|
766
|
+
}
|
|
767
|
+
`,
|
|
768
|
+
body: `part of '${name}_template.dart';
|
|
769
|
+
|
|
770
|
+
class _Wl${pascal}Body extends StatelessWidget {
|
|
771
|
+
final Wl${pascal}Data data;
|
|
772
|
+
final Wl${pascal}Callbacks callbacks;
|
|
773
|
+
|
|
774
|
+
const _Wl${pascal}Body({
|
|
775
|
+
required this.data,
|
|
776
|
+
required this.callbacks,
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
@override
|
|
780
|
+
Widget build(BuildContext context) {
|
|
781
|
+
final theme = context.theme;
|
|
782
|
+
return const SizedBox.shrink();
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
`,
|
|
786
|
+
skeleton: `part of '${name}_template.dart';
|
|
787
|
+
|
|
788
|
+
class _Wl${pascal}Skeleton extends StatelessWidget {
|
|
789
|
+
const _Wl${pascal}Skeleton();
|
|
790
|
+
|
|
791
|
+
@override
|
|
792
|
+
Widget build(BuildContext context) {
|
|
793
|
+
return const Center(
|
|
794
|
+
child: CircularProgressIndicator(),
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
`,
|
|
799
|
+
callbacks: `import 'package:flutter/material.dart';
|
|
800
|
+
|
|
801
|
+
class Wl${pascal}Callbacks {
|
|
802
|
+
final VoidCallback onBack;
|
|
803
|
+
final VoidCallback onRetry;
|
|
804
|
+
|
|
805
|
+
const Wl${pascal}Callbacks({
|
|
806
|
+
required this.onBack,
|
|
807
|
+
required this.onRetry,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
`,
|
|
811
|
+
config: `import 'package:flutter/material.dart';
|
|
812
|
+
import '../${name}_template.dart';
|
|
813
|
+
|
|
814
|
+
class Wl${pascal}Config extends Equatable {
|
|
815
|
+
final Wl${pascal}I18n i18n;
|
|
816
|
+
final bool showAppBar;
|
|
817
|
+
|
|
818
|
+
const Wl${pascal}Config({
|
|
819
|
+
this.i18n = Wl${pascal}I18n.empty,
|
|
820
|
+
this.showAppBar = true,
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
@override
|
|
824
|
+
List<Object?> get props => [i18n, showAppBar];
|
|
825
|
+
}
|
|
826
|
+
`,
|
|
827
|
+
data: `import 'package:flutter/material.dart';
|
|
828
|
+
|
|
829
|
+
class Wl${pascal}Data extends Equatable {
|
|
830
|
+
const Wl${pascal}Data();
|
|
831
|
+
|
|
832
|
+
@override
|
|
833
|
+
List<Object?> get props => [];
|
|
834
|
+
}
|
|
835
|
+
`
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function useCaseTemplate(name, pascal, tierPlural3) {
|
|
839
|
+
return `import 'package:widgetbook/widgetbook.dart';
|
|
840
|
+
import 'package:wl_design_system/${tierPlural3}/wl_${name}.dart';
|
|
841
|
+
|
|
842
|
+
WidgetbookUseCase wl${pascal}UseCase(BuildContext context) {
|
|
843
|
+
return WidgetbookUseCase(
|
|
844
|
+
name: 'Wl${pascal}',
|
|
845
|
+
builder: (context) => const Wl${pascal}(),
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
`;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/core/create-widget.ts
|
|
257
852
|
var SNAKE_CASE_REGEX2 = /^[a-z][a-z0-9_]*$/;
|
|
853
|
+
async function createWidget(name, tierInput, options) {
|
|
854
|
+
let cleanName = name;
|
|
855
|
+
if (cleanName.startsWith("wl_")) {
|
|
856
|
+
cleanName = cleanName.slice(3);
|
|
857
|
+
}
|
|
858
|
+
if (!cleanName || !SNAKE_CASE_REGEX2.test(cleanName)) {
|
|
859
|
+
throw new Error(
|
|
860
|
+
"Name must be snake_case (lowercase letters, digits, underscores)."
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
const tier = normalizeTier(tierInput);
|
|
864
|
+
const plural = tierPlural(tier);
|
|
865
|
+
const pascal = pascalCase2(cleanName);
|
|
866
|
+
const fileName = `wl_${cleanName}`;
|
|
867
|
+
const ds = detectDesignSystem(options.projectRoot);
|
|
868
|
+
if (!ds) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
"Could not detect design system. Ensure wl_design_system/ directory exists."
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
const tierDir = path6.join(ds.componentsDir, plural);
|
|
874
|
+
if (!fs6.existsSync(tierDir)) {
|
|
875
|
+
fs6.mkdirSync(tierDir, { recursive: true });
|
|
876
|
+
}
|
|
877
|
+
const pattern = options.pattern ?? getDefaultPattern(tier);
|
|
878
|
+
const flatPath = path6.join(tierDir, `${fileName}.dart`);
|
|
879
|
+
const subDirPath = path6.join(tierDir, fileName);
|
|
880
|
+
if (fs6.existsSync(flatPath) || fs6.existsSync(subDirPath)) {
|
|
881
|
+
throw new Error(`Widget "${fileName}" already exists in ${plural}/.`);
|
|
882
|
+
}
|
|
883
|
+
switch (tier) {
|
|
884
|
+
case "atom":
|
|
885
|
+
createAtomFiles(tierDir, cleanName, pascal, fileName, pattern);
|
|
886
|
+
break;
|
|
887
|
+
case "molecule":
|
|
888
|
+
createMoleculeFiles(tierDir, cleanName, pascal, fileName, pattern);
|
|
889
|
+
break;
|
|
890
|
+
case "organism":
|
|
891
|
+
createOrganismFiles(tierDir, cleanName, pascal, fileName, pattern);
|
|
892
|
+
break;
|
|
893
|
+
case "template":
|
|
894
|
+
createTemplateFiles(tierDir, cleanName, pascal, fileName, pattern);
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
const barrelFileName = `${plural}.dart`;
|
|
898
|
+
const exportLine = tier === "template" ? `export '${cleanName}/wl_${cleanName}_template.dart';` : `export '${fileName}.dart';`;
|
|
899
|
+
updateSortedBarrelFile(tierDir, barrelFileName, exportLine);
|
|
900
|
+
console.log(chalk2.green(`Widget "Wl${pascal}" created in ${plural}/`));
|
|
901
|
+
}
|
|
902
|
+
function createAtomFiles(tierDir, name, pascal, fileName, pattern) {
|
|
903
|
+
switch (pattern) {
|
|
904
|
+
case "single-public": {
|
|
905
|
+
fs6.writeFileSync(
|
|
906
|
+
path6.join(tierDir, `${fileName}.dart`),
|
|
907
|
+
atomTemplateA(name, pascal)
|
|
908
|
+
);
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
case "single-factory": {
|
|
912
|
+
fs6.writeFileSync(
|
|
913
|
+
path6.join(tierDir, `${fileName}.dart`),
|
|
914
|
+
atomTemplateB(name, pascal)
|
|
915
|
+
);
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
case "subdirectory-parts": {
|
|
919
|
+
const dir = path6.join(tierDir, fileName);
|
|
920
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
921
|
+
const tpl = atomTemplateC(name, pascal);
|
|
922
|
+
fs6.writeFileSync(path6.join(dir, `${fileName}.dart`), tpl.main);
|
|
923
|
+
fs6.writeFileSync(
|
|
924
|
+
path6.join(dir, `${fileName}_sizes.dart`),
|
|
925
|
+
tpl.sizes
|
|
926
|
+
);
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
default:
|
|
930
|
+
throw new Error(`Unknown atom pattern: ${pattern}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function createMoleculeFiles(tierDir, name, pascal, fileName, pattern) {
|
|
934
|
+
switch (pattern) {
|
|
935
|
+
case "single": {
|
|
936
|
+
fs6.writeFileSync(
|
|
937
|
+
path6.join(tierDir, `${fileName}.dart`),
|
|
938
|
+
moleculeTemplateA(name, pascal)
|
|
939
|
+
);
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case "subdirectory-parts": {
|
|
943
|
+
const dir = path6.join(tierDir, fileName);
|
|
944
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
945
|
+
const tpl = moleculeTemplateB(name, pascal);
|
|
946
|
+
fs6.writeFileSync(path6.join(dir, `${fileName}.dart`), tpl.main);
|
|
947
|
+
fs6.writeFileSync(
|
|
948
|
+
path6.join(dir, `${fileName}_type.dart`),
|
|
949
|
+
tpl.type
|
|
950
|
+
);
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
case "subdirectory-widgets": {
|
|
954
|
+
const dir = path6.join(tierDir, fileName);
|
|
955
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
956
|
+
fs6.mkdirSync(path6.join(dir, "widgets"), { recursive: true });
|
|
957
|
+
fs6.mkdirSync(path6.join(dir, "utils"), { recursive: true });
|
|
958
|
+
fs6.writeFileSync(
|
|
959
|
+
path6.join(dir, `${fileName}.dart`),
|
|
960
|
+
moleculeTemplateC(name, pascal)
|
|
961
|
+
);
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
default:
|
|
965
|
+
throw new Error(`Unknown molecule pattern: ${pattern}`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function createOrganismFiles(tierDir, name, pascal, fileName, pattern) {
|
|
969
|
+
switch (pattern) {
|
|
970
|
+
case "subdirectory-show": {
|
|
971
|
+
const dir = path6.join(tierDir, fileName);
|
|
972
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
973
|
+
const tpl = organismTemplateA(name, pascal);
|
|
974
|
+
fs6.writeFileSync(path6.join(dir, `${fileName}.dart`), tpl.main);
|
|
975
|
+
fs6.writeFileSync(
|
|
976
|
+
path6.join(dir, `${fileName}_content.dart`),
|
|
977
|
+
tpl.content
|
|
978
|
+
);
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
case "single": {
|
|
982
|
+
fs6.writeFileSync(
|
|
983
|
+
path6.join(tierDir, `${fileName}.dart`),
|
|
984
|
+
organismTemplateB(name, pascal)
|
|
985
|
+
);
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
default:
|
|
989
|
+
throw new Error(`Unknown organism pattern: ${pattern}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function createTemplateFiles(tierDir, name, pascal, fileName, pattern) {
|
|
993
|
+
const dir = path6.join(tierDir, name);
|
|
994
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
995
|
+
switch (pattern) {
|
|
996
|
+
case "simple": {
|
|
997
|
+
const tpl = templateA(name, pascal);
|
|
998
|
+
fs6.writeFileSync(
|
|
999
|
+
path6.join(dir, `wl_${name}_template.dart`),
|
|
1000
|
+
tpl.main
|
|
1001
|
+
);
|
|
1002
|
+
fs6.writeFileSync(
|
|
1003
|
+
path6.join(dir, `wl_${name}_i18n.dart`),
|
|
1004
|
+
tpl.i18n
|
|
1005
|
+
);
|
|
1006
|
+
fs6.writeFileSync(path6.join(dir, `wl_${name}_body.dart`), tpl.body);
|
|
1007
|
+
fs6.writeFileSync(
|
|
1008
|
+
path6.join(dir, `wl_${name}_skeleton.dart`),
|
|
1009
|
+
tpl.skeleton
|
|
1010
|
+
);
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
case "config-data-callbacks": {
|
|
1014
|
+
const tpl = templateB(name, pascal);
|
|
1015
|
+
fs6.writeFileSync(
|
|
1016
|
+
path6.join(dir, `wl_${name}_template.dart`),
|
|
1017
|
+
tpl.main
|
|
1018
|
+
);
|
|
1019
|
+
fs6.writeFileSync(
|
|
1020
|
+
path6.join(dir, `wl_${name}_i18n.dart`),
|
|
1021
|
+
tpl.i18n
|
|
1022
|
+
);
|
|
1023
|
+
fs6.writeFileSync(path6.join(dir, `wl_${name}_body.dart`), tpl.body);
|
|
1024
|
+
fs6.writeFileSync(
|
|
1025
|
+
path6.join(dir, `wl_${name}_skeleton.dart`),
|
|
1026
|
+
tpl.skeleton
|
|
1027
|
+
);
|
|
1028
|
+
const contractsDir = path6.join(dir, "contracts");
|
|
1029
|
+
fs6.mkdirSync(contractsDir, { recursive: true });
|
|
1030
|
+
fs6.writeFileSync(
|
|
1031
|
+
path6.join(contractsDir, `wl_${name}_callbacks.dart`),
|
|
1032
|
+
tpl.callbacks
|
|
1033
|
+
);
|
|
1034
|
+
fs6.writeFileSync(
|
|
1035
|
+
path6.join(contractsDir, `wl_${name}_config.dart`),
|
|
1036
|
+
tpl.config
|
|
1037
|
+
);
|
|
1038
|
+
fs6.writeFileSync(
|
|
1039
|
+
path6.join(contractsDir, `wl_${name}_data.dart`),
|
|
1040
|
+
tpl.data
|
|
1041
|
+
);
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
default:
|
|
1045
|
+
throw new Error(`Unknown template pattern: ${pattern}`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/core/create-usecase.ts
|
|
1050
|
+
import * as fs7 from "fs";
|
|
1051
|
+
import * as path7 from "path";
|
|
1052
|
+
import chalk3 from "chalk";
|
|
1053
|
+
import { pascalCase as pascalCase3 } from "change-case";
|
|
1054
|
+
var SNAKE_CASE_REGEX3 = /^[a-z][a-z0-9_]*$/;
|
|
1055
|
+
async function createUseCase(name, tierInput, options) {
|
|
1056
|
+
let cleanName = name;
|
|
1057
|
+
if (cleanName.startsWith("wl_")) {
|
|
1058
|
+
cleanName = cleanName.slice(3);
|
|
1059
|
+
}
|
|
1060
|
+
if (!cleanName || !SNAKE_CASE_REGEX3.test(cleanName)) {
|
|
1061
|
+
throw new Error(
|
|
1062
|
+
"Name must be snake_case (lowercase letters, digits, underscores)."
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
const tier = normalizeTier(tierInput);
|
|
1066
|
+
const plural = tierPlural(tier);
|
|
1067
|
+
const pascal = pascalCase3(cleanName);
|
|
1068
|
+
const fileName = `wl_${cleanName}`;
|
|
1069
|
+
const ds = detectDesignSystem(options.projectRoot);
|
|
1070
|
+
if (!ds) {
|
|
1071
|
+
throw new Error(
|
|
1072
|
+
"Could not detect design system. Ensure wl_design_system/ directory exists."
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (!ds.widgetbookDir) {
|
|
1076
|
+
throw new Error(
|
|
1077
|
+
"Could not detect widgetbook package. Ensure apps/widgetbook/ exists."
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (!widgetExists(ds.componentsDir, plural, fileName)) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
`Widget "${fileName}" not found in ${plural}/. Create the widget first.`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
const useCasesDir = path7.join(
|
|
1086
|
+
ds.widgetbookDir,
|
|
1087
|
+
"lib",
|
|
1088
|
+
"usecases",
|
|
1089
|
+
"wl_design_system"
|
|
1090
|
+
);
|
|
1091
|
+
if (!fs7.existsSync(useCasesDir)) {
|
|
1092
|
+
fs7.mkdirSync(useCasesDir, { recursive: true });
|
|
1093
|
+
}
|
|
1094
|
+
const useCasePath = path7.join(useCasesDir, `${fileName}.dart`);
|
|
1095
|
+
if (fs7.existsSync(useCasePath)) {
|
|
1096
|
+
throw new Error(`Use-case "${fileName}.dart" already exists.`);
|
|
1097
|
+
}
|
|
1098
|
+
fs7.writeFileSync(
|
|
1099
|
+
useCasePath,
|
|
1100
|
+
useCaseTemplate(cleanName, pascal, plural)
|
|
1101
|
+
);
|
|
1102
|
+
console.log(
|
|
1103
|
+
chalk3.green(
|
|
1104
|
+
`Use-case for "Wl${pascal}" created in widgetbook/lib/usecases/wl_design_system/`
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
if (options.buildRunner) {
|
|
1108
|
+
if (hasBuildRunner(ds.widgetbookDir)) {
|
|
1109
|
+
console.log(chalk3.blue("Running build_runner..."));
|
|
1110
|
+
await runBuildRunner(ds.widgetbookDir);
|
|
1111
|
+
console.log(chalk3.green("build_runner completed."));
|
|
1112
|
+
} else {
|
|
1113
|
+
console.log(
|
|
1114
|
+
chalk3.yellow(
|
|
1115
|
+
"Skipping build_runner (no build_runner dependency found in widgetbook)."
|
|
1116
|
+
)
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// src/interactive.ts
|
|
1123
|
+
import * as fs8 from "fs";
|
|
1124
|
+
import * as os from "os";
|
|
1125
|
+
import * as path8 from "path";
|
|
1126
|
+
import * as clack from "@clack/prompts";
|
|
1127
|
+
import chalk4 from "chalk";
|
|
1128
|
+
var SNAKE_CASE_REGEX4 = /^[a-z][a-z0-9_]*$/;
|
|
258
1129
|
async function resolveProject() {
|
|
259
1130
|
const s = clack.spinner();
|
|
260
1131
|
s.start("Analyzing current directory...");
|
|
261
1132
|
const cwdProject = analyzeProject(process.cwd());
|
|
262
1133
|
if (cwdProject && (cwdProject.hasFreezed || cwdProject.hasBloc)) {
|
|
263
|
-
s.stop(`Found ${
|
|
1134
|
+
s.stop(`Found ${chalk4.green(cwdProject.projectName)}`);
|
|
264
1135
|
return cwdProject;
|
|
265
1136
|
}
|
|
266
1137
|
s.message("Looking for Melos monorepo...");
|
|
@@ -273,8 +1144,8 @@ async function resolveProject() {
|
|
|
273
1144
|
}
|
|
274
1145
|
}
|
|
275
1146
|
s.message("Scanning for Flutter projects...");
|
|
276
|
-
const homeDev =
|
|
277
|
-
if (
|
|
1147
|
+
const homeDev = path8.join(os.homedir(), "Development");
|
|
1148
|
+
if (fs8.existsSync(homeDev)) {
|
|
278
1149
|
const projects = discoverProjects(homeDev, 2);
|
|
279
1150
|
if (projects.length > 0) {
|
|
280
1151
|
s.stop(`Found ${projects.length} Flutter project(s)`);
|
|
@@ -282,12 +1153,12 @@ async function resolveProject() {
|
|
|
282
1153
|
}
|
|
283
1154
|
}
|
|
284
1155
|
s.stop("No Flutter projects found");
|
|
285
|
-
clack.outro(
|
|
1156
|
+
clack.outro(chalk4.red("Could not find any Flutter project with freezed or flutter_bloc."));
|
|
286
1157
|
return null;
|
|
287
1158
|
}
|
|
288
1159
|
async function selectPackage(projects) {
|
|
289
1160
|
if (projects.length === 1) {
|
|
290
|
-
clack.log.info(`Using ${
|
|
1161
|
+
clack.log.info(`Using ${chalk4.green(projects[0].projectName)}`);
|
|
291
1162
|
return projects[0];
|
|
292
1163
|
}
|
|
293
1164
|
const selected = await clack.select({
|
|
@@ -295,7 +1166,7 @@ async function selectPackage(projects) {
|
|
|
295
1166
|
options: projects.map((p) => ({
|
|
296
1167
|
value: p,
|
|
297
1168
|
label: p.projectName,
|
|
298
|
-
hint:
|
|
1169
|
+
hint: path8.relative(os.homedir(), p.projectRoot)
|
|
299
1170
|
}))
|
|
300
1171
|
});
|
|
301
1172
|
if (clack.isCancel(selected)) {
|
|
@@ -304,16 +1175,14 @@ async function selectPackage(projects) {
|
|
|
304
1175
|
}
|
|
305
1176
|
return selected;
|
|
306
1177
|
}
|
|
307
|
-
async function
|
|
308
|
-
clack.intro(chalk2.bgCyan(chalk2.black(" wlmaker ")));
|
|
309
|
-
const project = await resolveProject();
|
|
310
|
-
if (!project) return;
|
|
1178
|
+
async function blocFlow(project) {
|
|
311
1179
|
const name = await clack.text({
|
|
312
1180
|
message: "BLoC name (snake_case)",
|
|
313
1181
|
placeholder: "e.g. user_login",
|
|
314
1182
|
validate: (value) => {
|
|
315
1183
|
if (!value.trim()) return "Name is required";
|
|
316
|
-
if (!
|
|
1184
|
+
if (!SNAKE_CASE_REGEX4.test(value))
|
|
1185
|
+
return "Must be snake_case (lowercase, digits, underscores)";
|
|
317
1186
|
}
|
|
318
1187
|
});
|
|
319
1188
|
if (clack.isCancel(name)) {
|
|
@@ -345,15 +1214,18 @@ async function interactiveMode() {
|
|
|
345
1214
|
clack.cancel("Cancelled");
|
|
346
1215
|
return;
|
|
347
1216
|
}
|
|
348
|
-
targetDir =
|
|
1217
|
+
targetDir = path8.resolve(customPath);
|
|
349
1218
|
} else {
|
|
350
|
-
targetDir =
|
|
1219
|
+
targetDir = path8.join(project.projectRoot, "lib", "features", feature);
|
|
351
1220
|
}
|
|
352
|
-
} else if (
|
|
353
|
-
targetDir =
|
|
354
|
-
clack.log.info(`Target: ${
|
|
1221
|
+
} else if (fs8.existsSync(path8.join(project.projectRoot, "lib", "bloc"))) {
|
|
1222
|
+
targetDir = path8.join(project.projectRoot, "lib", "bloc");
|
|
1223
|
+
clack.log.info(`Target: ${chalk4.cyan("lib/bloc/")}`);
|
|
355
1224
|
} else {
|
|
356
|
-
clack.note(
|
|
1225
|
+
clack.note(
|
|
1226
|
+
"No lib/features/ directory found. Provide a target path manually.",
|
|
1227
|
+
"Info"
|
|
1228
|
+
);
|
|
357
1229
|
const customPath = await clack.text({
|
|
358
1230
|
message: "Target directory path",
|
|
359
1231
|
placeholder: "lib/features/auth",
|
|
@@ -365,7 +1237,7 @@ async function interactiveMode() {
|
|
|
365
1237
|
clack.cancel("Cancelled");
|
|
366
1238
|
return;
|
|
367
1239
|
}
|
|
368
|
-
targetDir =
|
|
1240
|
+
targetDir = path8.resolve(customPath);
|
|
369
1241
|
}
|
|
370
1242
|
const defaultRun = project.hasBuildRunner;
|
|
371
1243
|
const runBuildRunner2 = await clack.confirm({
|
|
@@ -384,10 +1256,180 @@ async function interactiveMode() {
|
|
|
384
1256
|
buildRunner: runBuildRunner2
|
|
385
1257
|
});
|
|
386
1258
|
genSpinner.stop("BLoC generated");
|
|
387
|
-
clack.outro(
|
|
1259
|
+
clack.outro(chalk4.green("Done!"));
|
|
388
1260
|
} catch (error) {
|
|
389
1261
|
genSpinner.stop("Failed");
|
|
390
|
-
clack.outro(
|
|
1262
|
+
clack.outro(chalk4.red(`Error: ${error}`));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
async function widgetFlow(project) {
|
|
1266
|
+
const ds = detectDesignSystem(project.projectRoot);
|
|
1267
|
+
if (!ds) {
|
|
1268
|
+
clack.outro(
|
|
1269
|
+
chalk4.red(
|
|
1270
|
+
"No design system detected. Ensure wl_design_system/ directory exists."
|
|
1271
|
+
)
|
|
1272
|
+
);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
clack.log.info(
|
|
1276
|
+
`Design system: ${chalk4.cyan(path8.relative(project.projectRoot, ds.componentsDir))}`
|
|
1277
|
+
);
|
|
1278
|
+
const name = await clack.text({
|
|
1279
|
+
message: "Widget name (snake_case, without wl_ prefix)",
|
|
1280
|
+
placeholder: "e.g. toggle, badge, snackbar",
|
|
1281
|
+
validate: (value) => {
|
|
1282
|
+
if (!value.trim()) return "Name is required";
|
|
1283
|
+
const clean = value.startsWith("wl_") ? value.slice(3) : value;
|
|
1284
|
+
if (!SNAKE_CASE_REGEX4.test(clean))
|
|
1285
|
+
return "Must be snake_case (lowercase, digits, underscores)";
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
if (clack.isCancel(name)) {
|
|
1289
|
+
clack.cancel("Cancelled");
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const allTiers = VALID_TIERS.map((t) => ({
|
|
1293
|
+
value: t,
|
|
1294
|
+
label: tierLabel(t)
|
|
1295
|
+
}));
|
|
1296
|
+
const selectedTier = await clack.select({
|
|
1297
|
+
message: "Select tier",
|
|
1298
|
+
options: allTiers
|
|
1299
|
+
});
|
|
1300
|
+
if (clack.isCancel(selectedTier)) {
|
|
1301
|
+
clack.cancel("Cancelled");
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const tier = selectedTier;
|
|
1305
|
+
const patterns = getValidPatterns(tier);
|
|
1306
|
+
let selectedPattern = getDefaultPattern(tier);
|
|
1307
|
+
if (patterns.length > 1) {
|
|
1308
|
+
const patternChoice = await clack.select({
|
|
1309
|
+
message: "Select pattern",
|
|
1310
|
+
options: patterns.map((p) => ({
|
|
1311
|
+
value: p,
|
|
1312
|
+
label: patternLabel(p)
|
|
1313
|
+
}))
|
|
1314
|
+
});
|
|
1315
|
+
if (clack.isCancel(patternChoice)) {
|
|
1316
|
+
clack.cancel("Cancelled");
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
selectedPattern = patternChoice;
|
|
1320
|
+
}
|
|
1321
|
+
const genSpinner = clack.spinner();
|
|
1322
|
+
genSpinner.start("Generating widget files...");
|
|
1323
|
+
try {
|
|
1324
|
+
await createWidget(name, tier, {
|
|
1325
|
+
projectRoot: project.projectRoot,
|
|
1326
|
+
pattern: selectedPattern
|
|
1327
|
+
});
|
|
1328
|
+
genSpinner.stop("Widget generated");
|
|
1329
|
+
clack.outro(chalk4.green("Done!"));
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
genSpinner.stop("Failed");
|
|
1332
|
+
clack.outro(chalk4.red(`Error: ${error}`));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
async function useCaseFlow(project) {
|
|
1336
|
+
const ds = detectDesignSystem(project.projectRoot);
|
|
1337
|
+
if (!ds) {
|
|
1338
|
+
clack.outro(
|
|
1339
|
+
chalk4.red(
|
|
1340
|
+
"No design system detected. Ensure wl_design_system/ directory exists."
|
|
1341
|
+
)
|
|
1342
|
+
);
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
if (!ds.widgetbookDir) {
|
|
1346
|
+
clack.outro(
|
|
1347
|
+
chalk4.red(
|
|
1348
|
+
"No widgetbook package detected. Ensure apps/widgetbook/ exists."
|
|
1349
|
+
)
|
|
1350
|
+
);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const name = await clack.text({
|
|
1354
|
+
message: "Widget name for use-case (snake_case, without wl_ prefix)",
|
|
1355
|
+
placeholder: "e.g. toggle, badge",
|
|
1356
|
+
validate: (value) => {
|
|
1357
|
+
if (!value.trim()) return "Name is required";
|
|
1358
|
+
const clean = value.startsWith("wl_") ? value.slice(3) : value;
|
|
1359
|
+
if (!SNAKE_CASE_REGEX4.test(clean))
|
|
1360
|
+
return "Must be snake_case (lowercase, digits, underscores)";
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
if (clack.isCancel(name)) {
|
|
1364
|
+
clack.cancel("Cancelled");
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
const allTiers = VALID_TIERS.map((t) => ({
|
|
1368
|
+
value: t,
|
|
1369
|
+
label: tierLabel(t)
|
|
1370
|
+
}));
|
|
1371
|
+
const selectedTier = await clack.select({
|
|
1372
|
+
message: "Select widget tier",
|
|
1373
|
+
options: allTiers
|
|
1374
|
+
});
|
|
1375
|
+
if (clack.isCancel(selectedTier)) {
|
|
1376
|
+
clack.cancel("Cancelled");
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
const tier = selectedTier;
|
|
1380
|
+
const runBuildRunner2 = await clack.confirm({
|
|
1381
|
+
message: "Run build_runner after generation?",
|
|
1382
|
+
initialValue: true
|
|
1383
|
+
});
|
|
1384
|
+
if (clack.isCancel(runBuildRunner2)) {
|
|
1385
|
+
clack.cancel("Cancelled");
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
const genSpinner = clack.spinner();
|
|
1389
|
+
genSpinner.start("Generating use-case file...");
|
|
1390
|
+
try {
|
|
1391
|
+
await createUseCase(name, tier, {
|
|
1392
|
+
projectRoot: project.projectRoot,
|
|
1393
|
+
buildRunner: runBuildRunner2
|
|
1394
|
+
});
|
|
1395
|
+
genSpinner.stop("Use-case generated");
|
|
1396
|
+
clack.outro(chalk4.green("Done!"));
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
genSpinner.stop("Failed");
|
|
1399
|
+
clack.outro(chalk4.red(`Error: ${error}`));
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async function interactiveMode() {
|
|
1403
|
+
clack.intro(chalk4.bgCyan(chalk4.black(" wlmaker ")));
|
|
1404
|
+
const createType = await clack.select({
|
|
1405
|
+
message: "What do you want to create?",
|
|
1406
|
+
options: [
|
|
1407
|
+
{ value: "bloc", label: "BLoC", hint: "State management" },
|
|
1408
|
+
{ value: "widget", label: "Widget", hint: "Design system component" },
|
|
1409
|
+
{
|
|
1410
|
+
value: "usecase",
|
|
1411
|
+
label: "Widgetbook Use-Case",
|
|
1412
|
+
hint: "Component showcase"
|
|
1413
|
+
}
|
|
1414
|
+
]
|
|
1415
|
+
});
|
|
1416
|
+
if (clack.isCancel(createType)) {
|
|
1417
|
+
clack.cancel("Cancelled");
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const type = createType;
|
|
1421
|
+
const project = await resolveProject();
|
|
1422
|
+
if (!project) return;
|
|
1423
|
+
switch (type) {
|
|
1424
|
+
case "bloc":
|
|
1425
|
+
await blocFlow(project);
|
|
1426
|
+
break;
|
|
1427
|
+
case "widget":
|
|
1428
|
+
await widgetFlow(project);
|
|
1429
|
+
break;
|
|
1430
|
+
case "usecase":
|
|
1431
|
+
await useCaseFlow(project);
|
|
1432
|
+
break;
|
|
391
1433
|
}
|
|
392
1434
|
}
|
|
393
1435
|
|
|
@@ -395,19 +1437,49 @@ async function interactiveMode() {
|
|
|
395
1437
|
var require2 = createRequire(import.meta.url);
|
|
396
1438
|
var pkg = require2("../package.json");
|
|
397
1439
|
var program = new Command();
|
|
398
|
-
program.name("wlmaker").description(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
1440
|
+
program.name("wlmaker").description(
|
|
1441
|
+
"Create Flutter BLoCs, Widgets, and Widgetbook Use-Cases from the terminal"
|
|
1442
|
+
).version(pkg.version);
|
|
1443
|
+
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(
|
|
1444
|
+
async (name, options) => {
|
|
1445
|
+
if (!name) {
|
|
1446
|
+
await interactiveMode();
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
try {
|
|
1450
|
+
await createBloc(name, options);
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
console.error(chalk5.red(`Error: ${error}`));
|
|
1453
|
+
process.exit(1);
|
|
1454
|
+
}
|
|
403
1455
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
1456
|
+
);
|
|
1457
|
+
program.command("widget").description("Create a new widget in the design system").argument("<name>", "Widget name in snake_case (e.g. toggle)").requiredOption("-t, --tier <tier>", "tier: atom, molecule, organism, template").option("-p, --pattern <pattern>", "pattern (default depends on tier)").option("-d, --dir <path>", "project root directory", process.cwd()).action(
|
|
1458
|
+
async (name, options) => {
|
|
1459
|
+
try {
|
|
1460
|
+
await createWidget(name, options.tier, {
|
|
1461
|
+
projectRoot: options.dir,
|
|
1462
|
+
pattern: options.pattern
|
|
1463
|
+
});
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
console.error(chalk5.red(`Error: ${error}`));
|
|
1466
|
+
process.exit(1);
|
|
1467
|
+
}
|
|
409
1468
|
}
|
|
410
|
-
|
|
1469
|
+
);
|
|
1470
|
+
program.command("usecase").description("Create a Widgetbook use-case for an existing widget").argument("<name>", "Widget name in snake_case (e.g. toggle)").requiredOption("-t, --tier <tier>", "tier: atom, molecule, organism, template").option("-d, --dir <path>", "project root directory", process.cwd()).option("--no-build-runner", "skip build_runner execution").action(
|
|
1471
|
+
async (name, options) => {
|
|
1472
|
+
try {
|
|
1473
|
+
await createUseCase(name, options.tier, {
|
|
1474
|
+
projectRoot: options.dir,
|
|
1475
|
+
buildRunner: options.buildRunner
|
|
1476
|
+
});
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
console.error(chalk5.red(`Error: ${error}`));
|
|
1479
|
+
process.exit(1);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
411
1483
|
program.action(async () => {
|
|
412
1484
|
await interactiveMode();
|
|
413
1485
|
});
|