@tachui/cli 0.7.0-alpha1

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.
@@ -0,0 +1,734 @@
1
+ /**
2
+ * Tacho CLI - Init Command
3
+ *
4
+ * Initialize new TachUI projects with smart templates and Phase 6 features
5
+ */
6
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
7
+ import { join, resolve } from 'node:path';
8
+ import chalk from 'chalk';
9
+ import { Command } from 'commander';
10
+ import ora from 'ora';
11
+ import prompts from 'prompts';
12
+ const templates = {
13
+ basic: {
14
+ name: 'Basic TachUI App',
15
+ description: 'Simple TachUI application with core components',
16
+ features: ['Text & Button components', 'Layout system', 'Modifiers'],
17
+ files: {
18
+ 'package.json': JSON.stringify({
19
+ name: '{projectName}',
20
+ version: '1.0.0',
21
+ description: 'TachUI application',
22
+ type: 'module',
23
+ scripts: {
24
+ dev: 'vite',
25
+ build: 'vite build',
26
+ preview: 'vite preview',
27
+ typecheck: 'tsc --noEmit',
28
+ },
29
+ dependencies: {
30
+ '@tachui/core': '^0.1.0',
31
+ '@tachui/forms': '^0.1.0',
32
+ },
33
+ devDependencies: {
34
+ vite: '^5.0.0',
35
+ typescript: '^5.0.0',
36
+ '@types/node': '^20.0.0',
37
+ },
38
+ }, null, 2),
39
+ 'vite.config.ts': `import { defineConfig } from 'vite'
40
+
41
+ export default defineConfig({
42
+ server: {
43
+ port: 3000,
44
+ open: true
45
+ },
46
+ build: {
47
+ target: 'es2020'
48
+ }
49
+ })`,
50
+ 'tsconfig.json': JSON.stringify({
51
+ compilerOptions: {
52
+ target: 'ES2020',
53
+ module: 'ESNext',
54
+ moduleResolution: 'bundler',
55
+ allowSyntheticDefaultImports: true,
56
+ esModuleInterop: true,
57
+ jsx: 'preserve',
58
+ declaration: true,
59
+ strict: true,
60
+ skipLibCheck: true,
61
+ forceConsistentCasingInFileNames: true,
62
+ },
63
+ include: ['src/**/*'],
64
+ exclude: ['node_modules', 'dist'],
65
+ }, null, 2),
66
+ 'src/main.ts': `import { mount } from '@tachui/core'
67
+ import { App } from './App'
68
+
69
+ // Mount the app
70
+ mount('#app', App())`,
71
+ 'src/App.ts': `import { Layout, Text, Button } from '@tachui/core'
72
+
73
+ export function App() {
74
+ return Layout.VStack({
75
+ children: [
76
+ Text('Welcome to TachUI!')
77
+ .modifier
78
+ .fontSize(32)
79
+ .fontWeight('bold')
80
+ .foregroundColor('#007AFF')
81
+ .margin({ bottom: 16 })
82
+ .build(),
83
+
84
+ Text('SwiftUI-inspired web development')
85
+ .modifier
86
+ .fontSize(18)
87
+ .foregroundColor('#666')
88
+ .margin({ bottom: 24 })
89
+ .build(),
90
+
91
+ Button({
92
+ title: 'Get Started',
93
+ onTap: () => console.log('Hello TachUI!')
94
+ })
95
+ .modifier
96
+ .backgroundColor('#007AFF')
97
+ .foregroundColor('#ffffff')
98
+ .padding(16, 32)
99
+ .cornerRadius(8)
100
+ .build()
101
+ ],
102
+ spacing: 0,
103
+ alignment: 'center'
104
+ })
105
+ .modifier
106
+ .frame(undefined, '100vh')
107
+ .justifyContent('center')
108
+ .alignItems('center')
109
+ .backgroundColor('#f5f5f7')
110
+ .build()
111
+ }`,
112
+ 'index.html': `<!DOCTYPE html>
113
+ <html lang="en">
114
+ <head>
115
+ <meta charset="UTF-8">
116
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
117
+ <title>{projectName}</title>
118
+ <style>
119
+ * {
120
+ margin: 0;
121
+ padding: 0;
122
+ box-sizing: border-box;
123
+ }
124
+
125
+ body {
126
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
127
+ }
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <div id="app"></div>
132
+ <script type="module" src="/src/main.ts"></script>
133
+ </body>
134
+ </html>`,
135
+ 'README.md': `# {projectName}
136
+
137
+ A TachUI application built with SwiftUI-inspired components and reactive architecture.
138
+
139
+ ## Getting Started
140
+
141
+ \`\`\`bash
142
+ npm install
143
+ npm run dev
144
+ \`\`\`
145
+
146
+ ## Available Scripts
147
+
148
+ - \`npm run dev\` - Start development server
149
+ - \`npm run build\` - Build for production
150
+ - \`npm run preview\` - Preview production build
151
+ - \`npm run typecheck\` - Type check without building
152
+
153
+ ## Learn More
154
+
155
+ - [TachUI Documentation](https://github.com/whoughton/TachUI)
156
+ - [TachUI Examples](https://github.com/whoughton/TachUI/tree/main/examples)
157
+ `,
158
+ },
159
+ },
160
+ phase6: {
161
+ name: 'Phase 6 Features App',
162
+ description: 'Complete app with state management, lifecycle, and navigation',
163
+ features: [
164
+ '@State & @ObservedObject',
165
+ 'Lifecycle modifiers',
166
+ 'NavigationView & TabView',
167
+ 'Real-world patterns',
168
+ ],
169
+ files: {
170
+ 'package.json': JSON.stringify({
171
+ name: '{projectName}',
172
+ version: '1.0.0',
173
+ description: 'TachUI Phase 6 application with advanced features',
174
+ type: 'module',
175
+ scripts: {
176
+ dev: 'vite',
177
+ build: 'vite build',
178
+ preview: 'vite preview',
179
+ typecheck: 'tsc --noEmit',
180
+ },
181
+ dependencies: {
182
+ '@tachui/core': '^0.1.0',
183
+ '@tachui/forms': '^0.1.0',
184
+ },
185
+ devDependencies: {
186
+ vite: '^5.0.0',
187
+ typescript: '^5.0.0',
188
+ '@types/node': '^20.0.0',
189
+ },
190
+ }, null, 2),
191
+ 'vite.config.ts': `import { defineConfig } from 'vite'
192
+
193
+ export default defineConfig({
194
+ server: {
195
+ port: 3000,
196
+ open: true
197
+ },
198
+ build: {
199
+ target: 'es2020'
200
+ }
201
+ })`,
202
+ 'tsconfig.json': JSON.stringify({
203
+ compilerOptions: {
204
+ target: 'ES2020',
205
+ module: 'ESNext',
206
+ moduleResolution: 'bundler',
207
+ allowSyntheticDefaultImports: true,
208
+ esModuleInterop: true,
209
+ jsx: 'preserve',
210
+ declaration: true,
211
+ strict: true,
212
+ skipLibCheck: true,
213
+ forceConsistentCasingInFileNames: true,
214
+ },
215
+ include: ['src/**/*'],
216
+ exclude: ['node_modules', 'dist'],
217
+ }, null, 2),
218
+ 'src/main.ts': `import { mount } from '@tachui/core'
219
+ import { App } from './App'
220
+
221
+ // Mount the app
222
+ mount('#app', App())`,
223
+ 'src/App.ts': `import { TabView, createTabItem } from '@tachui/core/navigation'
224
+ import { State } from '@tachui/core/state'
225
+ import { HomeScreen } from './screens/HomeScreen'
226
+ import { TodoScreen } from './screens/TodoScreen'
227
+ import { SettingsScreen } from './screens/SettingsScreen'
228
+
229
+ export function App() {
230
+ const selectedTab = State('home')
231
+
232
+ const tabs = [
233
+ createTabItem(
234
+ 'home',
235
+ 'Home',
236
+ HomeScreen(),
237
+ { icon: '🏠' }
238
+ ),
239
+ createTabItem(
240
+ 'todos',
241
+ 'Todos',
242
+ TodoScreen(),
243
+ { icon: '📝' }
244
+ ),
245
+ createTabItem(
246
+ 'settings',
247
+ 'Settings',
248
+ SettingsScreen(),
249
+ { icon: '⚙️' }
250
+ )
251
+ ]
252
+
253
+ return TabView(tabs, {
254
+ selection: selectedTab.projectedValue,
255
+ tabPlacement: 'bottom',
256
+ accentColor: '#007AFF'
257
+ })
258
+ }`,
259
+ 'src/screens/HomeScreen.ts': `import { Layout, Text, Button } from '@tachui/core'
260
+ import { State } from '@tachui/core/state'
261
+
262
+ export function HomeScreen() {
263
+ const welcomeMessage = State('Welcome to TachUI Phase 6!')
264
+ const clickCount = State(0)
265
+
266
+ return Layout.VStack({
267
+ children: [
268
+ Text(() => welcomeMessage.wrappedValue)
269
+ .modifier
270
+ .fontSize(28)
271
+ .fontWeight('bold')
272
+ .foregroundColor('#007AFF')
273
+ .textAlign('center')
274
+ .margin({ bottom: 24 })
275
+ .build(),
276
+
277
+ Text('This app demonstrates all Phase 6 features:')
278
+ .modifier
279
+ .fontSize(18)
280
+ .foregroundColor('#333')
281
+ .margin({ bottom: 16 })
282
+ .build(),
283
+
284
+ Layout.VStack({
285
+ children: [
286
+ Text('✅ @State reactive property wrapper'),
287
+ Text('✅ Lifecycle modifiers (onAppear, task)'),
288
+ Text('✅ TabView navigation system'),
289
+ Text('✅ Real-world component patterns')
290
+ ].map(text =>
291
+ text.modifier
292
+ .fontSize(16)
293
+ .foregroundColor('#666')
294
+ .padding({ vertical: 4 })
295
+ .build()
296
+ ),
297
+ spacing: 4,
298
+ alignment: 'leading'
299
+ }),
300
+
301
+ Button({
302
+ title: \`Clicked \${() => clickCount.wrappedValue} times\`,
303
+ onTap: () => clickCount.wrappedValue++
304
+ })
305
+ .modifier
306
+ .backgroundColor('#007AFF')
307
+ .foregroundColor('#ffffff')
308
+ .padding(16, 24)
309
+ .cornerRadius(8)
310
+ .margin({ top: 32 })
311
+ .build()
312
+ ],
313
+ spacing: 0,
314
+ alignment: 'center'
315
+ })
316
+ .modifier
317
+ .padding(24)
318
+ .onAppear(() => {
319
+ console.log('Home screen appeared!')
320
+ })
321
+ .task(async () => {
322
+ // Simulate loading welcome message
323
+ await new Promise(resolve => setTimeout(resolve, 1000))
324
+ welcomeMessage.wrappedValue = 'Welcome to TachUI Phase 6! 🚀'
325
+ })
326
+ .build()
327
+ }`,
328
+ 'src/screens/TodoScreen.ts': `import { Layout, Text, Button } from '@tachui/core'
329
+ import { TextField } from '@tachui/forms'
330
+ import { State, ObservableObjectBase, ObservedObject } from '@tachui/core/state'
331
+
332
+ class TodoItem extends ObservableObjectBase {
333
+ constructor(
334
+ public id: string,
335
+ private _text: string,
336
+ private _completed: boolean = false
337
+ ) {
338
+ super()
339
+ }
340
+
341
+ get text() { return this._text }
342
+ set text(value: string) {
343
+ this._text = value
344
+ this.notifyChange()
345
+ }
346
+
347
+ get completed() { return this._completed }
348
+ set completed(value: boolean) {
349
+ this._completed = value
350
+ this.notifyChange()
351
+ }
352
+
353
+ toggle() {
354
+ this.completed = !this.completed
355
+ }
356
+ }
357
+
358
+ class TodoStore extends ObservableObjectBase {
359
+ private _items: TodoItem[] = []
360
+
361
+ get items() { return this._items }
362
+
363
+ addItem(text: string) {
364
+ const item = new TodoItem(Date.now().toString(), text)
365
+ this._items.push(item)
366
+ this.notifyChange()
367
+ }
368
+
369
+ removeItem(id: string) {
370
+ this._items = this._items.filter(item => item.id !== id)
371
+ this.notifyChange()
372
+ }
373
+
374
+ get completedCount() {
375
+ return this._items.filter(item => item.completed).length
376
+ }
377
+ }
378
+
379
+ export function TodoScreen() {
380
+ const todoStore = ObservedObject(new TodoStore())
381
+ const newTodoText = State('')
382
+
383
+ const addTodo = () => {
384
+ if (newTodoText.wrappedValue.trim()) {
385
+ todoStore.wrappedValue.addItem(newTodoText.wrappedValue)
386
+ newTodoText.wrappedValue = ''
387
+ }
388
+ }
389
+
390
+ return Layout.VStack({
391
+ children: [
392
+ Text('My Todos')
393
+ .modifier
394
+ .fontSize(28)
395
+ .fontWeight('bold')
396
+ .margin({ bottom: 16 })
397
+ .build(),
398
+
399
+ Text(() => \`\${todoStore.wrappedValue.completedCount} of \${todoStore.wrappedValue.items.length} completed\`)
400
+ .modifier
401
+ .fontSize(16)
402
+ .foregroundColor('#666')
403
+ .margin({ bottom: 20 })
404
+ .build(),
405
+
406
+ Layout.HStack({
407
+ children: [
408
+ TextField({
409
+ placeholder: 'Enter new todo',
410
+ text: newTodoText.projectedValue
411
+ })
412
+ .modifier
413
+ .flexGrow(1)
414
+ .build(),
415
+
416
+ Button({
417
+ title: 'Add',
418
+ onTap: addTodo
419
+ })
420
+ .modifier
421
+ .backgroundColor('#007AFF')
422
+ .foregroundColor('#ffffff')
423
+ .padding(8, 16)
424
+ .cornerRadius(6)
425
+ .build()
426
+ ],
427
+ spacing: 12
428
+ }),
429
+
430
+ Layout.VStack({
431
+ children: todoStore.wrappedValue.items.map(item => {
432
+ const observedItem = ObservedObject(item)
433
+
434
+ return Layout.HStack({
435
+ children: [
436
+ Button({
437
+ title: observedItem.wrappedValue.completed ? '✅' : '⬜',
438
+ onTap: () => observedItem.wrappedValue.toggle()
439
+ })
440
+ .modifier
441
+ .backgroundColor('transparent')
442
+ .border(0)
443
+ .padding(0)
444
+ .build(),
445
+
446
+ Text(() => observedItem.wrappedValue.text)
447
+ .modifier
448
+ .fontSize(16)
449
+ .foregroundColor(observedItem.wrappedValue.completed ? '#999' : '#333')
450
+ .textDecoration(observedItem.wrappedValue.completed ? 'line-through' : 'none')
451
+ .flexGrow(1)
452
+ .build(),
453
+
454
+ Button({
455
+ title: '🗑️',
456
+ onTap: () => todoStore.wrappedValue.removeItem(observedItem.wrappedValue.id)
457
+ })
458
+ .modifier
459
+ .backgroundColor('transparent')
460
+ .border(0)
461
+ .padding(0)
462
+ .build()
463
+ ],
464
+ spacing: 12,
465
+ alignment: 'center'
466
+ })
467
+ .modifier
468
+ .backgroundColor('#f8f9fa')
469
+ .padding(12)
470
+ .cornerRadius(8)
471
+ .margin({ bottom: 8 })
472
+ .build()
473
+ }),
474
+ spacing: 0
475
+ })
476
+ .modifier
477
+ .margin({ top: 20 })
478
+ .build()
479
+ ],
480
+ spacing: 0
481
+ })
482
+ .modifier
483
+ .padding(24)
484
+ .build()
485
+ }`,
486
+ 'src/screens/SettingsScreen.ts': `import { Layout, Text } from '@tachui/core'
487
+ import { State } from '@tachui/core/state'
488
+
489
+ export function SettingsScreen() {
490
+ const version = State('1.0.0')
491
+
492
+ return Layout.VStack({
493
+ children: [
494
+ Text('Settings')
495
+ .modifier
496
+ .fontSize(28)
497
+ .fontWeight('bold')
498
+ .margin({ bottom: 32 })
499
+ .build(),
500
+
501
+ Layout.VStack({
502
+ children: [
503
+ Text('App Information')
504
+ .modifier
505
+ .fontSize(20)
506
+ .fontWeight('semibold')
507
+ .margin({ bottom: 16 })
508
+ .build(),
509
+
510
+ Layout.HStack({
511
+ children: [
512
+ Text('Version:')
513
+ .modifier
514
+ .fontSize(16)
515
+ .foregroundColor('#666')
516
+ .build(),
517
+
518
+ Text(() => version.wrappedValue)
519
+ .modifier
520
+ .fontSize(16)
521
+ .fontWeight('medium')
522
+ .build()
523
+ ],
524
+ spacing: 8,
525
+ alignment: 'center'
526
+ }),
527
+
528
+ Layout.HStack({
529
+ children: [
530
+ Text('Framework:')
531
+ .modifier
532
+ .fontSize(16)
533
+ .foregroundColor('#666')
534
+ .build(),
535
+
536
+ Text('TachUI Phase 6')
537
+ .modifier
538
+ .fontSize(16)
539
+ .fontWeight('medium')
540
+ .build()
541
+ ],
542
+ spacing: 8,
543
+ alignment: 'center'
544
+ }),
545
+
546
+ Text('Built with SwiftUI-inspired components and reactive state management')
547
+ .modifier
548
+ .fontSize(14)
549
+ .foregroundColor('#999')
550
+ .textAlign('center')
551
+ .margin({ top: 24 })
552
+ .build()
553
+ ],
554
+ spacing: 12,
555
+ alignment: 'leading'
556
+ })
557
+ ],
558
+ spacing: 0
559
+ })
560
+ .modifier
561
+ .padding(24)
562
+ .build()
563
+ }`,
564
+ 'index.html': `<!DOCTYPE html>
565
+ <html lang="en">
566
+ <head>
567
+ <meta charset="UTF-8">
568
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
569
+ <title>{projectName}</title>
570
+ <style>
571
+ * {
572
+ margin: 0;
573
+ padding: 0;
574
+ box-sizing: border-box;
575
+ }
576
+
577
+ body {
578
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
579
+ background-color: #f5f5f7;
580
+ }
581
+ </style>
582
+ </head>
583
+ <body>
584
+ <div id="app"></div>
585
+ <script type="module" src="/src/main.ts"></script>
586
+ </body>
587
+ </html>`,
588
+ 'README.md': `# {projectName}
589
+
590
+ A complete TachUI application showcasing Phase 6 features:
591
+
592
+ - **@State**: Reactive local state management
593
+ - **@ObservedObject**: External object observation
594
+ - **Lifecycle Modifiers**: onAppear, task, refreshable
595
+ - **Navigation**: TabView with multiple screens
596
+ - **Real-world Patterns**: Todo app with persistent state
597
+
598
+ ## Getting Started
599
+
600
+ \`\`\`bash
601
+ npm install
602
+ npm run dev
603
+ \`\`\`
604
+
605
+ ## Features Demonstrated
606
+
607
+ ### State Management
608
+ - Local reactive state with \`@State\`
609
+ - Observable objects with \`@ObservedObject\`
610
+ - Property wrapper patterns from SwiftUI
611
+
612
+ ### Lifecycle Management
613
+ - \`onAppear\` for component initialization
614
+ - \`task\` for async operations with automatic cancellation
615
+ - Component lifecycle integration
616
+
617
+ ### Navigation System
618
+ - \`TabView\` for tab-based navigation
619
+ - Multiple screens with state preservation
620
+ - SwiftUI-style navigation patterns
621
+
622
+ ### Real-world Patterns
623
+ - Todo application with CRUD operations
624
+ - Observable data models
625
+ - Reactive UI updates
626
+ - Component composition
627
+
628
+ ## Available Scripts
629
+
630
+ - \`npm run dev\` - Start development server
631
+ - \`npm run build\` - Build for production
632
+ - \`npm run preview\` - Preview production build
633
+ - \`npm run typecheck\` - Type check without building
634
+
635
+ ## Learn More
636
+
637
+ - [TachUI Documentation](https://github.com/whoughton/TachUI)
638
+ - [Phase 6 Features Guide](https://github.com/whoughton/TachUI/blob/main/docs/api/phase-6-features.md)
639
+ `,
640
+ },
641
+ },
642
+ };
643
+ export const initCommand = new Command('init')
644
+ .description('Initialize a new TachUI project')
645
+ .argument('[project-name]', 'Project name')
646
+ .option('-t, --template <template>', 'Project template (basic, phase6)', 'basic')
647
+ .option('-y, --yes', 'Skip prompts and use defaults')
648
+ .action(async (projectName, options) => {
649
+ try {
650
+ let finalProjectName = projectName;
651
+ let selectedTemplate = options?.template || 'basic';
652
+ // Interactive prompts if not using --yes flag
653
+ if (!options?.yes) {
654
+ const response = await prompts([
655
+ {
656
+ type: 'text',
657
+ name: 'projectName',
658
+ message: 'Project name:',
659
+ initial: projectName || 'my-tachui-app',
660
+ validate: (value) => (value.length > 0 ? true : 'Project name is required'),
661
+ },
662
+ {
663
+ type: 'select',
664
+ name: 'template',
665
+ message: 'Choose a template:',
666
+ choices: Object.entries(templates).map(([key, template]) => ({
667
+ title: template.name,
668
+ description: template.description,
669
+ value: key,
670
+ })),
671
+ initial: selectedTemplate === 'phase6' ? 1 : 0,
672
+ },
673
+ ]);
674
+ if (!response.projectName) {
675
+ console.log(chalk.yellow('Operation cancelled'));
676
+ return;
677
+ }
678
+ finalProjectName = response.projectName;
679
+ selectedTemplate = response.template;
680
+ }
681
+ if (!finalProjectName) {
682
+ console.error(chalk.red('Project name is required'));
683
+ process.exit(1);
684
+ }
685
+ const template = templates[selectedTemplate];
686
+ if (!template) {
687
+ console.error(chalk.red(`Template "${selectedTemplate}" not found`));
688
+ process.exit(1);
689
+ }
690
+ const projectPath = resolve(finalProjectName);
691
+ // Check if directory already exists
692
+ if (existsSync(projectPath)) {
693
+ console.error(chalk.red(`Directory "${finalProjectName}" already exists`));
694
+ process.exit(1);
695
+ }
696
+ const spinner = ora('Creating TachUI project...').start();
697
+ // Create project directory
698
+ mkdirSync(projectPath, { recursive: true });
699
+ // Create all template files
700
+ for (const [filePath, content] of Object.entries(template.files)) {
701
+ const fullPath = join(projectPath, filePath);
702
+ const dir = fullPath.substring(0, fullPath.lastIndexOf('/'));
703
+ // Create directory if it doesn't exist
704
+ if (dir !== projectPath) {
705
+ mkdirSync(dir, { recursive: true });
706
+ }
707
+ // Replace template variables
708
+ const processedContent = content.replace(/{projectName}/g, finalProjectName);
709
+ writeFileSync(fullPath, processedContent);
710
+ }
711
+ spinner.succeed('TachUI project created successfully!');
712
+ // Success message with features
713
+ console.log(`
714
+ ${chalk.green('✅ Project created:')} ${chalk.cyan(finalProjectName)}
715
+ ${chalk.green('📁 Location:')} ${projectPath}
716
+ ${chalk.green('🎨 Template:')} ${template.name}
717
+
718
+ ${chalk.yellow('Features included:')}
719
+ ${template.features.map((feature) => ` • ${feature}`).join('\n')}
720
+
721
+ ${chalk.yellow('Next steps:')}
722
+ cd ${finalProjectName}
723
+ npm install
724
+ npm run dev
725
+
726
+ ${chalk.green('Happy coding with TachUI! 🚀')}
727
+ `);
728
+ }
729
+ catch (error) {
730
+ console.error(chalk.red('Error creating project:'), error.message);
731
+ process.exit(1);
732
+ }
733
+ });
734
+ //# sourceMappingURL=init.js.map