@tachui/cli 0.8.1-alpha → 0.8.8

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 (75) hide show
  1. package/README.md +37 -412
  2. package/bin/tacho.js +59 -7
  3. package/dist/commands/analyze-imports.d.ts +18 -0
  4. package/dist/commands/analyze-imports.d.ts.map +1 -0
  5. package/dist/commands/analyze-imports.js +152 -0
  6. package/dist/commands/analyze-imports.js.map +1 -0
  7. package/dist/commands/analyze.js +1 -1
  8. package/dist/commands/analyze.js.map +1 -1
  9. package/dist/commands/dev.js +3 -3
  10. package/dist/commands/dev.js.map +1 -1
  11. package/dist/commands/generate.d.ts +1 -1
  12. package/dist/commands/generate.d.ts.map +1 -1
  13. package/dist/commands/generate.js +1 -22
  14. package/dist/commands/generate.js.map +1 -1
  15. package/dist/commands/init.d.ts +10 -1
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +170 -680
  18. package/dist/commands/init.js.map +1 -1
  19. package/dist/commands/migrate/remove-modifier-trigger.d.ts +3 -0
  20. package/dist/commands/migrate/remove-modifier-trigger.d.ts.map +1 -0
  21. package/dist/commands/migrate/remove-modifier-trigger.js +103 -0
  22. package/dist/commands/migrate/remove-modifier-trigger.js.map +1 -0
  23. package/dist/commands/migrate.d.ts.map +1 -1
  24. package/dist/commands/migrate.js +8 -6
  25. package/dist/commands/migrate.js.map +1 -1
  26. package/dist/commands/modifier-docs.d.ts +24 -0
  27. package/dist/commands/modifier-docs.d.ts.map +1 -0
  28. package/dist/commands/modifier-docs.js +375 -0
  29. package/dist/commands/modifier-docs.js.map +1 -0
  30. package/dist/import-optimizer.d.ts +50 -0
  31. package/dist/import-optimizer.d.ts.map +1 -0
  32. package/dist/import-optimizer.js +227 -0
  33. package/dist/import-optimizer.js.map +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +14 -3
  36. package/dist/index.js.map +1 -1
  37. package/dist/migrations/remove-modifier-trigger.d.ts +7 -0
  38. package/dist/migrations/remove-modifier-trigger.d.ts.map +1 -0
  39. package/dist/migrations/remove-modifier-trigger.js +99 -0
  40. package/dist/migrations/remove-modifier-trigger.js.map +1 -0
  41. package/dist/scaffold/core-version-map.d.ts +2 -0
  42. package/dist/scaffold/core-version-map.d.ts.map +1 -0
  43. package/dist/scaffold/core-version-map.js +130 -0
  44. package/dist/scaffold/core-version-map.js.map +1 -0
  45. package/dist/scaffold/create-project.d.ts +15 -0
  46. package/dist/scaffold/create-project.d.ts.map +1 -0
  47. package/dist/scaffold/create-project.js +84 -0
  48. package/dist/scaffold/create-project.js.map +1 -0
  49. package/dist/scaffold/package-root.d.ts +2 -0
  50. package/dist/scaffold/package-root.d.ts.map +1 -0
  51. package/dist/scaffold/package-root.js +34 -0
  52. package/dist/scaffold/package-root.js.map +1 -0
  53. package/dist/scaffold/templates.d.ts +11 -0
  54. package/dist/scaffold/templates.d.ts.map +1 -0
  55. package/dist/scaffold/templates.js +26 -0
  56. package/dist/scaffold/templates.js.map +1 -0
  57. package/dist/scaffold/validators.d.ts +2 -0
  58. package/dist/scaffold/validators.d.ts.map +1 -0
  59. package/dist/scaffold/validators.js +32 -0
  60. package/dist/scaffold/validators.js.map +1 -0
  61. package/package.json +7 -3
  62. package/templates/advanced/README.md.template +23 -0
  63. package/templates/advanced/index.html.template +23 -0
  64. package/templates/advanced/package.json.template +20 -0
  65. package/templates/advanced/src/App.ts.template +54 -0
  66. package/templates/advanced/src/main.ts.template +4 -0
  67. package/templates/advanced/tsconfig.json.template +14 -0
  68. package/templates/advanced/vite.config.ts.template +11 -0
  69. package/templates/basic/README.md.template +17 -0
  70. package/templates/basic/index.html.template +22 -0
  71. package/templates/basic/package.json.template +20 -0
  72. package/templates/basic/src/App.ts.template +35 -0
  73. package/templates/basic/src/main.ts.template +4 -0
  74. package/templates/basic/tsconfig.json.template +14 -0
  75. package/templates/basic/vite.config.ts.template +11 -0
@@ -1,729 +1,219 @@
1
1
  /**
2
2
  * Tacho CLI - Init Command
3
3
  *
4
- * Initialize new TachUI projects with smart templates and Phase 6 features
4
+ * Initialize new TachUI projects from file-based templates.
5
5
  */
6
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
7
- import { join, resolve } from 'node:path';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { basename, join } from 'node:path';
8
8
  import chalk from 'chalk';
9
9
  import { Command } from 'commander';
10
10
  import ora from 'ora';
11
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;
12
+ import { createProject } from '../scaffold/create-project.js';
13
+ import { resolveCoreVersionFromMap } from '../scaffold/core-version-map.js';
14
+ import { resolvePackageRoot } from '../scaffold/package-root.js';
15
+ import { getTemplateDefinition, listTemplateDefinitions } from '../scaffold/templates.js';
16
+ import { validateProjectName } from '../scaffold/validators.js';
17
+ export function readCliVersion(packageRoot) {
18
+ const packageJsonPath = join(packageRoot, 'package.json');
19
+ if (!existsSync(packageJsonPath)) {
20
+ return null;
123
21
  }
124
-
125
- body {
126
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
22
+ try {
23
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
24
+ return typeof packageJson.version === 'string' ? packageJson.version : null;
25
+ }
26
+ catch {
27
+ return null;
127
28
  }
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
29
  }
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
- }
30
+ export function isValidSemverLike(value) {
31
+ return typeof value === 'string' && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
377
32
  }
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 = ''
33
+ export async function resolveLatestPublishedCoreVersion() {
34
+ const registryLatestUrl = process.env.TACHUI_CORE_LATEST_URL || 'https://registry.npmjs.org/@tachui/core/latest';
35
+ const controller = new AbortController();
36
+ const timeout = setTimeout(() => controller.abort(), 2500);
37
+ try {
38
+ const response = await fetch(registryLatestUrl, {
39
+ signal: controller.signal,
40
+ });
41
+ if (!response.ok) {
42
+ return null;
43
+ }
44
+ const body = (await response.json());
45
+ return isValidSemverLike(body.version) ? body.version : null;
387
46
  }
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;
47
+ catch {
48
+ return null;
575
49
  }
576
-
577
- body {
578
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
579
- background-color: #f5f5f7;
50
+ finally {
51
+ clearTimeout(timeout);
580
52
  }
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
- };
53
+ }
54
+ export async function resolveDefaultTachuiVersion(cliVersion, resolveRegistryVersion = resolveLatestPublishedCoreVersion) {
55
+ const registryVersion = await resolveRegistryVersion();
56
+ if (registryVersion) {
57
+ return {
58
+ version: registryVersion,
59
+ source: 'registry',
60
+ };
61
+ }
62
+ const mappedVersion = resolveCoreVersionFromMap(cliVersion);
63
+ if (mappedVersion) {
64
+ return {
65
+ version: mappedVersion,
66
+ source: 'compatibility-map',
67
+ };
68
+ }
69
+ throw new Error('Unable to determine a default @tachui/core version. Pass --tachui-version explicitly.');
70
+ }
71
+ function printTemplates() {
72
+ console.log(chalk.cyan('\nAvailable templates:\n'));
73
+ for (const template of listTemplateDefinitions()) {
74
+ console.log(`${chalk.green(template.id)} - ${template.description}`);
75
+ }
76
+ console.log('');
77
+ }
78
+ function resolveInstallCommand(packageManager) {
79
+ return packageManager === 'pnpm' ? 'pnpm install' : 'npm install';
80
+ }
81
+ function resolveDevCommand(packageManager) {
82
+ return packageManager === 'pnpm' ? 'pnpm dev' : 'npm run dev';
83
+ }
84
+ function validateTargetToProjectName(target, cwd) {
85
+ const projectName = target === '.' ? basename(cwd) : basename(target);
86
+ return validateProjectName(projectName);
87
+ }
643
88
  export const initCommand = new Command('init')
644
89
  .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) => {
90
+ .argument('[target]', 'Project directory name (use "." for current directory)')
91
+ .option('-t, --template <template>', 'Project template (basic, advanced)', 'basic')
92
+ .option('-y, --yes', 'Skip prompts and use provided options')
93
+ .option('--tachui-version <version>', 'TachUI package version to scaffold')
94
+ .option('--package-manager <packageManager>', 'Package manager for next-step instructions (npm, pnpm)', 'npm')
95
+ .option('--list-templates', 'List available templates')
96
+ .action(async (target, options) => {
649
97
  try {
650
- let finalProjectName = projectName;
651
- let selectedTemplate = options?.template || 'basic';
652
- // Interactive prompts if not using --yes flag
98
+ const packageRoot = resolvePackageRoot(import.meta.url);
99
+ const templatesRoot = join(packageRoot, 'templates');
100
+ const cliVersion = readCliVersion(packageRoot);
101
+ if (options?.listTemplates) {
102
+ printTemplates();
103
+ return;
104
+ }
105
+ const resolvedDefaultVersion = options?.tachuiVersion
106
+ ? null
107
+ : await resolveDefaultTachuiVersion(cliVersion);
108
+ if (resolvedDefaultVersion?.source === 'compatibility-map') {
109
+ console.log(chalk.yellow('Warning: could not reach npm registry; using CLI compatibility map for default TachUI version.'));
110
+ }
111
+ let finalTarget = target;
112
+ let finalTemplateId = (options?.template || 'basic').toLowerCase();
113
+ let finalTachuiVersion = options?.tachuiVersion || resolvedDefaultVersion?.version || '';
114
+ let finalPackageManager = options?.packageManager || 'npm';
653
115
  if (!options?.yes) {
654
- const response = await prompts([
116
+ const responses = await prompts([
655
117
  {
656
118
  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'),
119
+ name: 'target',
120
+ message: 'Project directory:',
121
+ initial: finalTarget || 'my-tachui-app',
122
+ validate: (value) => {
123
+ const result = validateTargetToProjectName(value, process.cwd());
124
+ return result ?? true;
125
+ },
661
126
  },
662
127
  {
663
128
  type: 'select',
664
129
  name: 'template',
665
130
  message: 'Choose a template:',
666
- choices: Object.entries(templates).map(([key, template]) => ({
131
+ choices: listTemplateDefinitions().map(template => ({
667
132
  title: template.name,
668
133
  description: template.description,
669
- value: key,
134
+ value: template.id,
670
135
  })),
671
- initial: selectedTemplate === 'phase6' ? 1 : 0,
136
+ },
137
+ {
138
+ type: 'text',
139
+ name: 'tachuiVersion',
140
+ message: 'TachUI version for generated dependencies:',
141
+ initial: finalTachuiVersion,
142
+ validate: (value) => isValidSemverLike(value.trim())
143
+ ? true
144
+ : 'Version must be a valid semver string (for example 0.8.8-alpha)',
145
+ },
146
+ {
147
+ type: 'select',
148
+ name: 'packageManager',
149
+ message: 'Package manager:',
150
+ choices: [
151
+ { title: 'npm', value: 'npm' },
152
+ { title: 'pnpm', value: 'pnpm' },
153
+ ],
154
+ initial: finalPackageManager === 'pnpm' ? 1 : 0,
672
155
  },
673
156
  ]);
674
- if (!response.projectName) {
157
+ if (!responses.target) {
675
158
  console.log(chalk.yellow('Operation cancelled'));
676
159
  return;
677
160
  }
678
- finalProjectName = response.projectName;
679
- selectedTemplate = response.template;
161
+ if (!responses.template || !responses.tachuiVersion || !responses.packageManager) {
162
+ console.log(chalk.yellow('Operation cancelled'));
163
+ return;
164
+ }
165
+ finalTarget = responses.target;
166
+ finalTemplateId = responses.template;
167
+ finalTachuiVersion = responses.tachuiVersion;
168
+ finalPackageManager = responses.packageManager;
680
169
  }
681
- if (!finalProjectName) {
682
- console.error(chalk.red('Project name is required'));
170
+ else if (!finalTarget) {
171
+ console.error(chalk.red('Project target is required when using --yes'));
683
172
  process.exit(1);
684
173
  }
685
- const template = templates[selectedTemplate];
686
- if (!template) {
687
- console.error(chalk.red(`Template "${selectedTemplate}" not found`));
174
+ if (!finalTarget) {
175
+ console.error(chalk.red('Project target is required'));
688
176
  process.exit(1);
689
177
  }
690
- const projectPath = resolve(finalProjectName);
691
- // Check if directory already exists
692
- if (existsSync(projectPath)) {
693
- console.error(chalk.red(`Directory "${finalProjectName}" already exists`));
178
+ finalTachuiVersion = finalTachuiVersion.trim();
179
+ if (!isValidSemverLike(finalTachuiVersion)) {
180
+ console.error(chalk.red(`Invalid --tachui-version "${finalTachuiVersion}". Expected a semver value like 0.8.8-alpha.`));
694
181
  process.exit(1);
695
182
  }
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);
183
+ const template = getTemplateDefinition(finalTemplateId);
184
+ if (!template) {
185
+ const available = listTemplateDefinitions()
186
+ .map(item => item.id)
187
+ .join(', ');
188
+ console.error(chalk.red(`Unknown template "${finalTemplateId}". Available templates: ${available}`));
189
+ process.exit(1);
710
190
  }
711
- spinner.succeed('TachUI project created successfully!');
712
- // Success message with features
191
+ const packageManager = finalPackageManager === 'pnpm' ? 'pnpm' : 'npm';
192
+ const spinner = ora('Creating TachUI project...').start();
193
+ const result = createProject({
194
+ cwd: process.cwd(),
195
+ target: finalTarget,
196
+ tachuiVersion: finalTachuiVersion,
197
+ template,
198
+ templatesRoot,
199
+ });
200
+ spinner.succeed('Project created successfully');
201
+ const installCommand = resolveInstallCommand(packageManager);
202
+ const devCommand = resolveDevCommand(packageManager);
203
+ const cdLine = finalTarget === '.' ? null : `cd ${finalTarget}`;
713
204
  console.log(`
714
- ${chalk.green('Project created:')} ${chalk.cyan(finalProjectName)}
715
- ${chalk.green('📁 Location:')} ${projectPath}
716
- ${chalk.green('🎨 Template:')} ${template.name}
205
+ ${chalk.green('Project:')} ${chalk.cyan(result.projectName)}
206
+ ${chalk.green('Location:')} ${result.projectPath}
207
+ ${chalk.green('Template:')} ${template.name}
208
+ ${chalk.green('TachUI version:')} ${finalTachuiVersion}
209
+ ${chalk.green('Files created:')} ${result.createdFiles}
717
210
 
718
- ${chalk.yellow('Features included:')}
719
- ${template.features.map((feature) => ` ${feature}`).join('\n')}
211
+ ${chalk.yellow('Included features:')}
212
+ ${template.features.map(feature => ` - ${feature}`).join('\n')}
720
213
 
721
214
  ${chalk.yellow('Next steps:')}
722
- cd ${finalProjectName}
723
- npm install
724
- npm run dev
725
-
726
- ${chalk.green('Happy coding with TachUI! 🚀')}
215
+ ${cdLine ? ` ${cdLine}\n` : ''} ${installCommand}
216
+ ${devCommand}
727
217
  `);
728
218
  }
729
219
  catch (error) {