@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.
- package/LICENSE +363 -0
- package/bin/tacho.js +10 -0
- package/dist/commands/analyze.d.ts +8 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +337 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/dev.d.ts +8 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +171 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/generate.d.ts +8 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +716 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +734 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/migrate.d.ts +8 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +335 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/optimize.d.ts +8 -0
- package/dist/commands/optimize.d.ts.map +1 -0
- package/dist/commands/optimize.js +357 -0
- package/dist/commands/optimize.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tacho CLI - Generate Command
|
|
3
|
+
*
|
|
4
|
+
* Code generation and scaffolding for TachUI components with Phase 6 patterns
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { 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 generators = {
|
|
13
|
+
component: {
|
|
14
|
+
name: 'Basic Component',
|
|
15
|
+
description: 'Generate a basic TachUI component with modifiers',
|
|
16
|
+
prompts: [
|
|
17
|
+
{
|
|
18
|
+
type: 'text',
|
|
19
|
+
name: 'description',
|
|
20
|
+
message: 'Component description:',
|
|
21
|
+
initial: 'A TachUI component',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'confirm',
|
|
25
|
+
name: 'withState',
|
|
26
|
+
message: 'Include @State example?',
|
|
27
|
+
initial: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'confirm',
|
|
31
|
+
name: 'withModifiers',
|
|
32
|
+
message: 'Include modifier examples?',
|
|
33
|
+
initial: true,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
generate: (answers, componentName) => ({
|
|
37
|
+
[`src/components/${componentName}.ts`]: `import { Layout, Text, Button } from '@tachui/core'${answers.withState
|
|
38
|
+
? `
|
|
39
|
+
import { State } from '@tachui/core/state'`
|
|
40
|
+
: ''}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* ${componentName}
|
|
44
|
+
*
|
|
45
|
+
* ${answers.description}
|
|
46
|
+
*/
|
|
47
|
+
export function ${componentName}() {${answers.withState
|
|
48
|
+
? `
|
|
49
|
+
const isActive = State(false)`
|
|
50
|
+
: ''}
|
|
51
|
+
|
|
52
|
+
return Layout.VStack({
|
|
53
|
+
children: [
|
|
54
|
+
Text('${componentName} Component')${answers.withModifiers
|
|
55
|
+
? `
|
|
56
|
+
.modifier
|
|
57
|
+
.fontSize(20)
|
|
58
|
+
.fontWeight('bold')
|
|
59
|
+
.foregroundColor('#007AFF')
|
|
60
|
+
.build()`
|
|
61
|
+
: ''},${answers.withState
|
|
62
|
+
? `
|
|
63
|
+
|
|
64
|
+
Text(() => \`Status: \${isActive.wrappedValue ? 'Active' : 'Inactive'}\`)${answers.withModifiers
|
|
65
|
+
? `
|
|
66
|
+
.modifier
|
|
67
|
+
.fontSize(16)
|
|
68
|
+
.foregroundColor('#666')
|
|
69
|
+
.margin({ bottom: 16 })
|
|
70
|
+
.build()`
|
|
71
|
+
: ''},
|
|
72
|
+
|
|
73
|
+
Button({
|
|
74
|
+
title: 'Toggle State',
|
|
75
|
+
onTap: () => isActive.wrappedValue = !isActive.wrappedValue
|
|
76
|
+
})${answers.withModifiers
|
|
77
|
+
? `
|
|
78
|
+
.modifier
|
|
79
|
+
.backgroundColor('#007AFF')
|
|
80
|
+
.foregroundColor('#ffffff')
|
|
81
|
+
.padding(12, 24)
|
|
82
|
+
.cornerRadius(8)
|
|
83
|
+
.build()`
|
|
84
|
+
: ''}`
|
|
85
|
+
: ''}
|
|
86
|
+
],
|
|
87
|
+
spacing: 12,
|
|
88
|
+
alignment: 'center'
|
|
89
|
+
})${answers.withModifiers
|
|
90
|
+
? `
|
|
91
|
+
.modifier
|
|
92
|
+
.padding(24)
|
|
93
|
+
.backgroundColor('#f8f9fa')
|
|
94
|
+
.cornerRadius(12)
|
|
95
|
+
.build()`
|
|
96
|
+
: ''}
|
|
97
|
+
}`,
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
screen: {
|
|
101
|
+
name: 'Screen Component',
|
|
102
|
+
description: 'Generate a screen component with navigation support',
|
|
103
|
+
prompts: [
|
|
104
|
+
{
|
|
105
|
+
type: 'text',
|
|
106
|
+
name: 'description',
|
|
107
|
+
message: 'Screen description:',
|
|
108
|
+
initial: 'A TachUI screen',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'confirm',
|
|
112
|
+
name: 'withNavigation',
|
|
113
|
+
message: 'Include navigation examples?',
|
|
114
|
+
initial: true,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: 'confirm',
|
|
118
|
+
name: 'withLifecycle',
|
|
119
|
+
message: 'Include lifecycle modifiers?',
|
|
120
|
+
initial: true,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: 'confirm',
|
|
124
|
+
name: 'withState',
|
|
125
|
+
message: 'Include state management?',
|
|
126
|
+
initial: true,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
generate: (answers, componentName) => ({
|
|
130
|
+
[`src/screens/${componentName}.ts`]: `import { Layout, Text, Button } from '@tachui/core'${answers.withState
|
|
131
|
+
? `
|
|
132
|
+
import { State } from '@tachui/core/state'`
|
|
133
|
+
: ''}${answers.withNavigation
|
|
134
|
+
? `
|
|
135
|
+
import { NavigationLink, useNavigation } from '@tachui/navigation'`
|
|
136
|
+
: ''}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* ${componentName}
|
|
140
|
+
*
|
|
141
|
+
* ${answers.description}
|
|
142
|
+
*/
|
|
143
|
+
export function ${componentName}() {${answers.withState
|
|
144
|
+
? `
|
|
145
|
+
const isLoading = State(false)
|
|
146
|
+
const data = State<string>('')`
|
|
147
|
+
: ''}${answers.withNavigation
|
|
148
|
+
? `
|
|
149
|
+
const navigation = useNavigation()`
|
|
150
|
+
: ''}
|
|
151
|
+
|
|
152
|
+
return Layout.VStack({
|
|
153
|
+
children: [
|
|
154
|
+
Text('${componentName}')
|
|
155
|
+
.modifier
|
|
156
|
+
.fontSize(28)
|
|
157
|
+
.fontWeight('bold')
|
|
158
|
+
.textAlign('center')
|
|
159
|
+
.margin({ bottom: 24 })
|
|
160
|
+
.build(),${answers.withState
|
|
161
|
+
? `
|
|
162
|
+
|
|
163
|
+
Text(() => isLoading.wrappedValue ? 'Loading...' : 'Ready')
|
|
164
|
+
.modifier
|
|
165
|
+
.fontSize(18)
|
|
166
|
+
.foregroundColor('#666')
|
|
167
|
+
.margin({ bottom: 16 })
|
|
168
|
+
.build(),`
|
|
169
|
+
: ''}${answers.withNavigation
|
|
170
|
+
? `
|
|
171
|
+
|
|
172
|
+
NavigationLink(
|
|
173
|
+
() => DetailScreen(),
|
|
174
|
+
Text('Go to Detail')
|
|
175
|
+
.modifier
|
|
176
|
+
.fontSize(16)
|
|
177
|
+
.foregroundColor('#007AFF')
|
|
178
|
+
.build()
|
|
179
|
+
),
|
|
180
|
+
|
|
181
|
+
Button({
|
|
182
|
+
title: 'Go Back',
|
|
183
|
+
onTap: () => navigation.pop()
|
|
184
|
+
})
|
|
185
|
+
.modifier
|
|
186
|
+
.backgroundColor('#f0f0f0')
|
|
187
|
+
.foregroundColor('#333')
|
|
188
|
+
.padding(12, 24)
|
|
189
|
+
.cornerRadius(8)
|
|
190
|
+
.margin({ top: 16 })
|
|
191
|
+
.build()`
|
|
192
|
+
: ''}
|
|
193
|
+
],
|
|
194
|
+
spacing: 0,
|
|
195
|
+
alignment: 'center'
|
|
196
|
+
})
|
|
197
|
+
.modifier
|
|
198
|
+
.padding(24)
|
|
199
|
+
.frame(undefined, '100vh')
|
|
200
|
+
.justifyContent('center')${answers.withLifecycle
|
|
201
|
+
? `
|
|
202
|
+
.onAppear(() => {
|
|
203
|
+
console.log('${componentName} appeared')${answers.withState
|
|
204
|
+
? `
|
|
205
|
+
isLoading.wrappedValue = true`
|
|
206
|
+
: ''}
|
|
207
|
+
})${answers.withState
|
|
208
|
+
? `
|
|
209
|
+
.task(async () => {
|
|
210
|
+
// Simulate data loading
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
212
|
+
data.wrappedValue = 'Loaded data for ${componentName}'
|
|
213
|
+
isLoading.wrappedValue = false
|
|
214
|
+
})`
|
|
215
|
+
: ''}`
|
|
216
|
+
: ''}
|
|
217
|
+
.build()
|
|
218
|
+
}`,
|
|
219
|
+
}),
|
|
220
|
+
},
|
|
221
|
+
store: {
|
|
222
|
+
name: 'Observable Store',
|
|
223
|
+
description: 'Generate a store class with @ObservedObject pattern',
|
|
224
|
+
prompts: [
|
|
225
|
+
{
|
|
226
|
+
type: 'text',
|
|
227
|
+
name: 'description',
|
|
228
|
+
message: 'Store description:',
|
|
229
|
+
initial: 'A data store',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: 'text',
|
|
233
|
+
name: 'dataType',
|
|
234
|
+
message: 'Primary data type:',
|
|
235
|
+
initial: 'string',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: 'confirm',
|
|
239
|
+
name: 'withCRUD',
|
|
240
|
+
message: 'Include CRUD operations?',
|
|
241
|
+
initial: true,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
type: 'confirm',
|
|
245
|
+
name: 'withPersistence',
|
|
246
|
+
message: 'Include localStorage persistence?',
|
|
247
|
+
initial: false,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
generate: (answers, componentName) => {
|
|
251
|
+
const storeName = componentName;
|
|
252
|
+
// const itemName = componentName.replace(/Store$/, '')
|
|
253
|
+
return {
|
|
254
|
+
[`src/stores/${storeName}.ts`]: `import { ObservableObjectBase } from '@tachui/core/state'
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* ${storeName}
|
|
258
|
+
*
|
|
259
|
+
* ${answers.description}
|
|
260
|
+
*/
|
|
261
|
+
export class ${storeName} extends ObservableObjectBase {
|
|
262
|
+
private _items: ${answers.dataType}[] = []${answers.withPersistence
|
|
263
|
+
? `
|
|
264
|
+
private readonly STORAGE_KEY = '${storeName.toLowerCase()}_data'`
|
|
265
|
+
: ''}
|
|
266
|
+
|
|
267
|
+
constructor() {
|
|
268
|
+
super()${answers.withPersistence
|
|
269
|
+
? `
|
|
270
|
+
this.loadFromStorage()`
|
|
271
|
+
: ''}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get items(): ${answers.dataType}[] {
|
|
275
|
+
return this._items
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
get count(): number {
|
|
279
|
+
return this._items.length
|
|
280
|
+
}${answers.withCRUD
|
|
281
|
+
? `
|
|
282
|
+
|
|
283
|
+
add(item: ${answers.dataType}): void {
|
|
284
|
+
this._items.push(item)
|
|
285
|
+
this.notifyChange()${answers.withPersistence
|
|
286
|
+
? `
|
|
287
|
+
this.saveToStorage()`
|
|
288
|
+
: ''}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
remove(index: number): void {
|
|
292
|
+
if (index >= 0 && index < this._items.length) {
|
|
293
|
+
this._items.splice(index, 1)
|
|
294
|
+
this.notifyChange()${answers.withPersistence
|
|
295
|
+
? `
|
|
296
|
+
this.saveToStorage()`
|
|
297
|
+
: ''}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
update(index: number, item: ${answers.dataType}): void {
|
|
302
|
+
if (index >= 0 && index < this._items.length) {
|
|
303
|
+
this._items[index] = item
|
|
304
|
+
this.notifyChange()${answers.withPersistence
|
|
305
|
+
? `
|
|
306
|
+
this.saveToStorage()`
|
|
307
|
+
: ''}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
clear(): void {
|
|
312
|
+
this._items = []
|
|
313
|
+
this.notifyChange()${answers.withPersistence
|
|
314
|
+
? `
|
|
315
|
+
this.saveToStorage()`
|
|
316
|
+
: ''}
|
|
317
|
+
}`
|
|
318
|
+
: ''}${answers.withPersistence
|
|
319
|
+
? `
|
|
320
|
+
|
|
321
|
+
private saveToStorage(): void {
|
|
322
|
+
try {
|
|
323
|
+
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this._items))
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.warn('Failed to save to localStorage:', error)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private loadFromStorage(): void {
|
|
330
|
+
try {
|
|
331
|
+
const stored = localStorage.getItem(this.STORAGE_KEY)
|
|
332
|
+
if (stored) {
|
|
333
|
+
this._items = JSON.parse(stored)
|
|
334
|
+
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.warn('Failed to load from localStorage:', error)
|
|
337
|
+
this._items = []
|
|
338
|
+
}
|
|
339
|
+
}`
|
|
340
|
+
: ''}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Example usage:
|
|
344
|
+
// const store = new ${storeName}()
|
|
345
|
+
// const observedStore = ObservedObject(store)
|
|
346
|
+
//
|
|
347
|
+
// In component:
|
|
348
|
+
// observedStore.wrappedValue.add("new item")
|
|
349
|
+
`,
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
form: {
|
|
354
|
+
name: 'Form Component',
|
|
355
|
+
description: 'Generate a form component with validation',
|
|
356
|
+
prompts: [
|
|
357
|
+
{
|
|
358
|
+
type: 'text',
|
|
359
|
+
name: 'description',
|
|
360
|
+
message: 'Form description:',
|
|
361
|
+
initial: 'A form component',
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
type: 'confirm',
|
|
365
|
+
name: 'withValidation',
|
|
366
|
+
message: 'Include form validation?',
|
|
367
|
+
initial: true,
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
type: 'confirm',
|
|
371
|
+
name: 'withSubmission',
|
|
372
|
+
message: 'Include form submission handler?',
|
|
373
|
+
initial: true,
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
generate: (answers, componentName) => ({
|
|
377
|
+
[`src/components/${componentName}.ts`]: `import { Layout, Text, Button } from '@tachui/core'
|
|
378
|
+
import { TextField } from '@tachui/forms'
|
|
379
|
+
import { State } from '@tachui/core/state'
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* ${componentName}
|
|
383
|
+
*
|
|
384
|
+
* ${answers.description}
|
|
385
|
+
*/
|
|
386
|
+
export function ${componentName}() {
|
|
387
|
+
const formData = State({
|
|
388
|
+
name: '',
|
|
389
|
+
email: '',
|
|
390
|
+
message: ''
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
const isSubmitting = State(false)${answers.withValidation
|
|
394
|
+
? `
|
|
395
|
+
const errors = State<Record<string, string>>({})`
|
|
396
|
+
: ''}${answers.withValidation
|
|
397
|
+
? `
|
|
398
|
+
|
|
399
|
+
const validateForm = () => {
|
|
400
|
+
const newErrors: Record<string, string> = {}
|
|
401
|
+
|
|
402
|
+
if (!formData.wrappedValue.name.trim()) {
|
|
403
|
+
newErrors.name = 'Name is required'
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!formData.wrappedValue.email.trim()) {
|
|
407
|
+
newErrors.email = 'Email is required'
|
|
408
|
+
} else if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.wrappedValue.email)) {
|
|
409
|
+
newErrors.email = 'Invalid email format'
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!formData.wrappedValue.message.trim()) {
|
|
413
|
+
newErrors.message = 'Message is required'
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
errors.wrappedValue = newErrors
|
|
417
|
+
return Object.keys(newErrors).length === 0
|
|
418
|
+
}`
|
|
419
|
+
: ''}${answers.withSubmission
|
|
420
|
+
? `
|
|
421
|
+
|
|
422
|
+
const handleSubmit = async () => {${answers.withValidation
|
|
423
|
+
? `
|
|
424
|
+
if (!validateForm()) {
|
|
425
|
+
return
|
|
426
|
+
}`
|
|
427
|
+
: ''}
|
|
428
|
+
|
|
429
|
+
isSubmitting.wrappedValue = true
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
// Simulate form submission
|
|
433
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
434
|
+
|
|
435
|
+
console.log('Form submitted:', formData.wrappedValue)
|
|
436
|
+
|
|
437
|
+
// Reset form
|
|
438
|
+
formData.wrappedValue = {
|
|
439
|
+
name: '',
|
|
440
|
+
email: '',
|
|
441
|
+
message: ''
|
|
442
|
+
}${answers.withValidation
|
|
443
|
+
? `
|
|
444
|
+
|
|
445
|
+
errors.wrappedValue = {}`
|
|
446
|
+
: ''}
|
|
447
|
+
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error('Submission failed:', error)
|
|
450
|
+
} finally {
|
|
451
|
+
isSubmitting.wrappedValue = false
|
|
452
|
+
}
|
|
453
|
+
}`
|
|
454
|
+
: ''}
|
|
455
|
+
|
|
456
|
+
return Layout.VStack({
|
|
457
|
+
children: [
|
|
458
|
+
Text('${componentName}')
|
|
459
|
+
.modifier
|
|
460
|
+
.fontSize(24)
|
|
461
|
+
.fontWeight('bold')
|
|
462
|
+
.margin({ bottom: 24 })
|
|
463
|
+
.build(),
|
|
464
|
+
|
|
465
|
+
// Name field
|
|
466
|
+
Layout.VStack({
|
|
467
|
+
children: [
|
|
468
|
+
Text('Name')
|
|
469
|
+
.modifier
|
|
470
|
+
.fontSize(16)
|
|
471
|
+
.fontWeight('medium')
|
|
472
|
+
.margin({ bottom: 8 })
|
|
473
|
+
.build(),
|
|
474
|
+
|
|
475
|
+
TextField({
|
|
476
|
+
placeholder: 'Enter your name',
|
|
477
|
+
text: formData.projectedValue.map(
|
|
478
|
+
(data) => data.name,
|
|
479
|
+
(newName, data) => ({ ...data, name: newName })
|
|
480
|
+
)
|
|
481
|
+
})
|
|
482
|
+
.modifier
|
|
483
|
+
.padding(12)
|
|
484
|
+
.border('1px solid #ddd')
|
|
485
|
+
.cornerRadius(8)
|
|
486
|
+
.build(),${answers.withValidation
|
|
487
|
+
? `
|
|
488
|
+
|
|
489
|
+
...(errors.wrappedValue.name ? [
|
|
490
|
+
Text(errors.wrappedValue.name)
|
|
491
|
+
.modifier
|
|
492
|
+
.fontSize(14)
|
|
493
|
+
.foregroundColor('#ef4444')
|
|
494
|
+
.margin({ top: 4 })
|
|
495
|
+
.build()
|
|
496
|
+
] : [])`
|
|
497
|
+
: ''}
|
|
498
|
+
],
|
|
499
|
+
spacing: 0,
|
|
500
|
+
alignment: 'leading'
|
|
501
|
+
}),
|
|
502
|
+
|
|
503
|
+
// Email field
|
|
504
|
+
Layout.VStack({
|
|
505
|
+
children: [
|
|
506
|
+
Text('Email')
|
|
507
|
+
.modifier
|
|
508
|
+
.fontSize(16)
|
|
509
|
+
.fontWeight('medium')
|
|
510
|
+
.margin({ bottom: 8 })
|
|
511
|
+
.build(),
|
|
512
|
+
|
|
513
|
+
TextField({
|
|
514
|
+
placeholder: 'Enter your email',
|
|
515
|
+
text: formData.projectedValue.map(
|
|
516
|
+
(data) => data.email,
|
|
517
|
+
(newEmail, data) => ({ ...data, email: newEmail })
|
|
518
|
+
)
|
|
519
|
+
})
|
|
520
|
+
.modifier
|
|
521
|
+
.padding(12)
|
|
522
|
+
.border('1px solid #ddd')
|
|
523
|
+
.cornerRadius(8)
|
|
524
|
+
.build(),${answers.withValidation
|
|
525
|
+
? `
|
|
526
|
+
|
|
527
|
+
...(errors.wrappedValue.email ? [
|
|
528
|
+
Text(errors.wrappedValue.email)
|
|
529
|
+
.modifier
|
|
530
|
+
.fontSize(14)
|
|
531
|
+
.foregroundColor('#ef4444')
|
|
532
|
+
.margin({ top: 4 })
|
|
533
|
+
.build()
|
|
534
|
+
] : [])`
|
|
535
|
+
: ''}
|
|
536
|
+
],
|
|
537
|
+
spacing: 0,
|
|
538
|
+
alignment: 'leading'
|
|
539
|
+
}),
|
|
540
|
+
|
|
541
|
+
// Message field
|
|
542
|
+
Layout.VStack({
|
|
543
|
+
children: [
|
|
544
|
+
Text('Message')
|
|
545
|
+
.modifier
|
|
546
|
+
.fontSize(16)
|
|
547
|
+
.fontWeight('medium')
|
|
548
|
+
.margin({ bottom: 8 })
|
|
549
|
+
.build(),
|
|
550
|
+
|
|
551
|
+
TextField({
|
|
552
|
+
placeholder: 'Enter your message',
|
|
553
|
+
text: formData.projectedValue.map(
|
|
554
|
+
(data) => data.message,
|
|
555
|
+
(newMessage, data) => ({ ...data, message: newMessage })
|
|
556
|
+
)
|
|
557
|
+
})
|
|
558
|
+
.modifier
|
|
559
|
+
.padding(12)
|
|
560
|
+
.border('1px solid #ddd')
|
|
561
|
+
.cornerRadius(8)
|
|
562
|
+
.minHeight(100)
|
|
563
|
+
.build(),${answers.withValidation
|
|
564
|
+
? `
|
|
565
|
+
|
|
566
|
+
...(errors.wrappedValue.message ? [
|
|
567
|
+
Text(errors.wrappedValue.message)
|
|
568
|
+
.modifier
|
|
569
|
+
.fontSize(14)
|
|
570
|
+
.foregroundColor('#ef4444')
|
|
571
|
+
.margin({ top: 4 })
|
|
572
|
+
.build()
|
|
573
|
+
] : [])`
|
|
574
|
+
: ''}
|
|
575
|
+
],
|
|
576
|
+
spacing: 0,
|
|
577
|
+
alignment: 'leading'
|
|
578
|
+
}),
|
|
579
|
+
|
|
580
|
+
// Submit button
|
|
581
|
+
Button({
|
|
582
|
+
title: isSubmitting.wrappedValue ? 'Submitting...' : 'Submit',
|
|
583
|
+
onTap: ${answers.withSubmission ? 'handleSubmit' : '() => console.log("Form submitted:", formData.wrappedValue)'},
|
|
584
|
+
disabled: isSubmitting.wrappedValue
|
|
585
|
+
})
|
|
586
|
+
.modifier
|
|
587
|
+
.backgroundColor(isSubmitting.wrappedValue ? '#ccc' : '#007AFF')
|
|
588
|
+
.foregroundColor('#ffffff')
|
|
589
|
+
.padding(16, 32)
|
|
590
|
+
.cornerRadius(8)
|
|
591
|
+
.margin({ top: 24 })
|
|
592
|
+
.build()
|
|
593
|
+
],
|
|
594
|
+
spacing: 16,
|
|
595
|
+
alignment: 'stretch'
|
|
596
|
+
})
|
|
597
|
+
.modifier
|
|
598
|
+
.padding(24)
|
|
599
|
+
.maxWidth(500)
|
|
600
|
+
.build()
|
|
601
|
+
}`,
|
|
602
|
+
}),
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
export const generateCommand = new Command('generate')
|
|
606
|
+
.description('Generate TachUI components and code')
|
|
607
|
+
.alias('g')
|
|
608
|
+
.argument('[type]', 'Generator type (component, screen, store, form)')
|
|
609
|
+
.argument('[name]', 'Component name')
|
|
610
|
+
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
611
|
+
.option('-d, --dir <directory>', 'Output directory')
|
|
612
|
+
.action(async (type, name, options) => {
|
|
613
|
+
try {
|
|
614
|
+
let selectedType = type;
|
|
615
|
+
let componentName = name;
|
|
616
|
+
// Show available generators if no type specified
|
|
617
|
+
if (!selectedType) {
|
|
618
|
+
const response = await prompts({
|
|
619
|
+
type: 'select',
|
|
620
|
+
name: 'type',
|
|
621
|
+
message: 'What would you like to generate?',
|
|
622
|
+
choices: Object.entries(generators).map(([key, generator]) => ({
|
|
623
|
+
title: generator.name,
|
|
624
|
+
description: generator.description,
|
|
625
|
+
value: key,
|
|
626
|
+
})),
|
|
627
|
+
});
|
|
628
|
+
if (!response.type) {
|
|
629
|
+
console.log(chalk.yellow('Operation cancelled'));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
selectedType = response.type;
|
|
633
|
+
}
|
|
634
|
+
const generator = generators[selectedType];
|
|
635
|
+
if (!generator) {
|
|
636
|
+
console.error(chalk.red(`Generator "${selectedType}" not found`));
|
|
637
|
+
console.log(chalk.yellow('Available generators:'), Object.keys(generators).join(', '));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
// Get component name if not provided
|
|
641
|
+
if (!componentName) {
|
|
642
|
+
const response = await prompts({
|
|
643
|
+
type: 'text',
|
|
644
|
+
name: 'name',
|
|
645
|
+
message: `${generator.name} name:`,
|
|
646
|
+
validate: (value) => {
|
|
647
|
+
if (!value.trim())
|
|
648
|
+
return 'Name is required';
|
|
649
|
+
if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) {
|
|
650
|
+
return 'Name must start with capital letter and contain only letters/numbers';
|
|
651
|
+
}
|
|
652
|
+
return true;
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
if (!response.name) {
|
|
656
|
+
console.log(chalk.yellow('Operation cancelled'));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
componentName = response.name;
|
|
660
|
+
}
|
|
661
|
+
// Validate component name
|
|
662
|
+
if (!componentName || !/^[A-Z][a-zA-Z0-9]*$/.test(componentName)) {
|
|
663
|
+
console.error(chalk.red('Component name must start with capital letter and contain only letters/numbers'));
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
// Get generator-specific configuration
|
|
667
|
+
let answers = {};
|
|
668
|
+
if (!options?.yes && generator.prompts.length > 0) {
|
|
669
|
+
answers = await prompts(generator.prompts);
|
|
670
|
+
}
|
|
671
|
+
const spinner = ora(`Generating ${generator.name}...`).start();
|
|
672
|
+
// Generate files
|
|
673
|
+
const files = generator.generate(answers, componentName);
|
|
674
|
+
const baseDir = options?.dir || process.cwd();
|
|
675
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
676
|
+
const fullPath = resolve(baseDir, filePath);
|
|
677
|
+
const dir = fullPath.substring(0, fullPath.lastIndexOf('/'));
|
|
678
|
+
// Create directory if it doesn't exist
|
|
679
|
+
if (!existsSync(dir)) {
|
|
680
|
+
mkdirSync(dir, { recursive: true });
|
|
681
|
+
}
|
|
682
|
+
// Check if file already exists
|
|
683
|
+
if (existsSync(fullPath)) {
|
|
684
|
+
const overwrite = await prompts({
|
|
685
|
+
type: 'confirm',
|
|
686
|
+
name: 'overwrite',
|
|
687
|
+
message: `File ${filePath} already exists. Overwrite?`,
|
|
688
|
+
initial: false,
|
|
689
|
+
});
|
|
690
|
+
if (!overwrite.overwrite) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
writeFileSync(fullPath, content);
|
|
695
|
+
}
|
|
696
|
+
spinner.succeed(`${generator.name} generated successfully!`);
|
|
697
|
+
// Show created files
|
|
698
|
+
console.log(`\n${chalk.green('✅ Files created:')}`);
|
|
699
|
+
Object.keys(files).forEach((filePath) => {
|
|
700
|
+
console.log(chalk.gray(` ${filePath}`));
|
|
701
|
+
});
|
|
702
|
+
// Show usage instructions
|
|
703
|
+
console.log(`\n${chalk.yellow('💡 Usage:')}`);
|
|
704
|
+
const importPath = Object.keys(files)[0]
|
|
705
|
+
.replace(/^src\//, './')
|
|
706
|
+
.replace(/\.ts$/, '');
|
|
707
|
+
console.log(chalk.gray(` import { ${componentName} } from '${importPath}'`));
|
|
708
|
+
console.log(chalk.gray(` // Use ${componentName}() in your app`));
|
|
709
|
+
console.log(`\n${chalk.green('Happy coding with TachUI! 🚀')}`);
|
|
710
|
+
}
|
|
711
|
+
catch (error) {
|
|
712
|
+
console.error(chalk.red('Error generating code:'), error.message);
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
//# sourceMappingURL=generate.js.map
|