@vertz/create-vertz-app 0.2.20 → 0.2.22

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.
@@ -1,20 +1,24 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ import { readFileSync } from 'node:fs';
4
+ import { resolve } from 'node:path';
3
5
  import { Command } from 'commander';
4
- import { resolveOptions, scaffold } from '../dist/index.js';
6
+
7
+ const pkg = JSON.parse(readFileSync(resolve(import.meta.dir, '../package.json'), 'utf-8'));
5
8
 
6
9
  const program = new Command();
7
10
 
8
11
  program
9
12
  .name('create-vertz-app')
10
13
  .description('Scaffold a new Vertz project')
11
- .version('0.1.0')
14
+ .version(pkg.version)
12
15
  .argument('[name]', 'Project name')
13
16
  .action(async (name: string | undefined) => {
17
+ const { resolveOptions, scaffold } = await import('../dist/index.js');
14
18
  try {
15
19
  const resolved = await resolveOptions({ projectName: name });
16
20
 
17
- console.log(`Creating Vertz app: ${resolved.projectName}`);
21
+ console.log(`Creating Vertz app: ${resolved.projectName} (v${pkg.version})`);
18
22
 
19
23
  const targetDir = process.cwd();
20
24
  await scaffold(targetDir, resolved);
@@ -59,7 +59,7 @@ export declare function serverTemplate(): string;
59
59
  */
60
60
  export declare function schemaTemplate(): string;
61
61
  /**
62
- * src/api/db.ts — createSqliteAdapter with autoApply migrations
62
+ * src/api/db.ts — createDb with local SQLite and autoApply migrations
63
63
  */
64
64
  export declare function dbTemplate(): string;
65
65
  /**
@@ -79,11 +79,13 @@ export declare function appComponentTemplate(): string;
79
79
  */
80
80
  export declare function entryClientTemplate(): string;
81
81
  /**
82
- * src/styles/theme.ts — configureThemeBase from @vertz/theme-shadcn/base
82
+ * src/styles/theme.ts — configureTheme from @vertz/theme-shadcn
83
83
  */
84
84
  export declare function themeTemplate(): string;
85
85
  /**
86
- * src/pages/home.tsx — task list + create form with query + css
86
+ * src/pages/home.tsx — full CRUD task list with form, checkbox toggle,
87
+ * delete confirmation dialog, and animated list transitions.
88
+ * Demonstrates theme components (Button, Input, AlertDialog) over raw HTML.
87
89
  */
88
90
  export declare function homePageTemplate(): string;
89
91
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA6B5D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAyJnD;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CA0NlD;AAID;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA6B/D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAoBzC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAIvC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAc9C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA6B1C;AAID;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAW1C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAoBvC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAavC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAOvC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAgD7C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAWtC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CA0IzC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA6B5D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAyJnD;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAqQlD;AAID;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA6B/D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAoBzC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAIvC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAc9C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA6B1C;AAID;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAW1C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAoBvC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAavC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAWnC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAOvC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAgD7C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CActC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CA0LzC"}
@@ -266,7 +266,7 @@ Never use \`appendChild\`, \`innerHTML\`, \`textContent\`, \`document.createElem
266
266
 
267
267
  \`\`\`tsx
268
268
  // RIGHT
269
- return <div class={styles.panel}>{title}</div>;
269
+ return <div className={styles.panel}>{title}</div>;
270
270
 
271
271
  // WRONG — no imperative DOM
272
272
  const el = document.createElement('div');
@@ -288,7 +288,7 @@ TaskCard({ task, onClick: handleClick });
288
288
  \`\`\`tsx
289
289
  {isLoading && <div>Loading...</div>}
290
290
 
291
- {error ? <div class={styles.error}>{error.message}</div> : <div>{content}</div>}
291
+ {error ? <div className={styles.error}>{error.message}</div> : <div>{content}</div>}
292
292
 
293
293
  {tasks.map((task) => (
294
294
  <TaskItem key={task.id} task={task} />
@@ -328,40 +328,83 @@ After mutations (\`create\`, \`update\`, \`delete\`), related queries are automa
328
328
  refetched in the background. No manual \`refetch()\` calls needed — the framework
329
329
  handles cache invalidation via optimistic updates.
330
330
 
331
- ## Styling
331
+ ## Theme Components — Prefer Over Raw HTML
332
+
333
+ When a themed component exists, use it instead of raw HTML elements with manual class names.
334
+ Theme components are pre-configured with the app's design tokens and provide consistent styling.
335
+
336
+ ### Using Components
332
337
 
333
- ### \`css()\` for scoped styles
338
+ Import components from \`@vertz/ui/components\` the centralized entrypoint:
334
339
 
335
340
  \`\`\`tsx
336
- const styles = css({
337
- container: ['flex', 'flex-col', 'gap:4', 'p:6'],
338
- title: ['font:xl', 'font:bold', 'text:foreground'],
339
- card: ['rounded:md', 'border:1', 'border:border', 'bg:card', 'p:4'],
340
- });
341
+ import { Button, Input, AlertDialog } from '@vertz/ui/components';
342
+
343
+ // RIGHT use theme components
344
+ <Button intent="primary" size="md">Submit</Button>
345
+ <Input placeholder="Enter text" />
346
+
347
+ // WRONG — raw HTML with manual styles
348
+ <button className={button({ intent: 'primary', size: 'md' })}>Submit</button>
349
+ <input className={inputStyles.base} placeholder="Enter text" />
350
+ \`\`\`
351
+
352
+ ### Available Components
353
+
354
+ **Direct**: \`Button\`, \`Input\`, \`Label\`, \`Badge\`, \`Textarea\`,
355
+ \`Card\` suite, \`Table\` suite, \`Avatar\` suite, \`FormGroup\` suite
341
356
 
342
- return <div class={styles.container}>...</div>;
357
+ **Primitives**: \`AlertDialog\`, \`Dialog\`, \`Tabs\`,
358
+ \`Select\`, \`DropdownMenu\`, \`Popover\`, \`Sheet\`, \`Tooltip\`, \`Accordion\`
359
+ — all with sub-components (\`.Trigger\`, \`.Content\`, \`.Footer\`, etc.)
360
+
361
+ ## Dialogs
362
+
363
+ ### Composable \`<AlertDialog>\` for inline confirmations
364
+
365
+ \`\`\`tsx
366
+ import { Button, AlertDialog } from '@vertz/ui/components';
367
+
368
+ <AlertDialog>
369
+ <AlertDialog.Trigger>
370
+ <Button intent="danger" size="sm">Delete</Button>
371
+ </AlertDialog.Trigger>
372
+ <AlertDialog.Content>
373
+ <AlertDialog.Title>Delete task?</AlertDialog.Title>
374
+ <AlertDialog.Description>This action cannot be undone.</AlertDialog.Description>
375
+ <AlertDialog.Footer>
376
+ <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
377
+ <AlertDialog.Action onClick={handleDelete}>Delete</AlertDialog.Action>
378
+ </AlertDialog.Footer>
379
+ </AlertDialog.Content>
380
+ </AlertDialog>
343
381
  \`\`\`
344
382
 
345
- ### \`variants()\` for parameterized styles
383
+ ### \`useDialogStack()\` for imperative/stacked dialogs
384
+
385
+ Use when you need promise-based results or dialogs opened from event handlers:
346
386
 
347
387
  \`\`\`tsx
348
- const button = variants({
349
- base: ['inline-flex', 'items:center', 'rounded:md', 'font:medium'],
350
- variants: {
351
- intent: {
352
- primary: ['bg:primary.600', 'text:white'],
353
- secondary: ['bg:secondary', 'text:secondary-foreground'],
354
- danger: ['bg:destructive', 'text:white'],
355
- },
356
- size: {
357
- sm: ['text:xs', 'px:3', 'py:1'],
358
- md: ['text:sm', 'px:4', 'py:2'],
359
- },
360
- },
361
- defaultVariants: { intent: 'primary', size: 'md' },
388
+ import { useDialogStack } from 'vertz/ui';
389
+
390
+ const dialogs = useDialogStack();
391
+ const confirmed = await dialogs.open(ConfirmDialog, { message: 'Delete?' });
392
+ if (confirmed) handleDelete();
393
+ \`\`\`
394
+
395
+ ## Styling
396
+
397
+ ### \`css()\` for layout and custom styles
398
+
399
+ Use \`css()\` for layout-specific styles that don't correspond to a theme component:
400
+
401
+ \`\`\`tsx
402
+ const styles = css({
403
+ container: ['flex', 'flex-col', 'gap:4', 'p:6'],
404
+ heading: ['font:xl', 'font:bold', 'text:foreground'],
362
405
  });
363
406
 
364
- <button class={button({ intent: 'danger', size: 'sm' })}>Delete</button>
407
+ return <div className={styles.container}>...</div>;
365
408
  \`\`\`
366
409
 
367
410
  ### Style Tokens
@@ -615,7 +658,7 @@ export function schemaTemplate() {
615
658
 
616
659
  export const tasksTable = d.table('tasks', {
617
660
  id: d.uuid().primary(),
618
- title: d.text(),
661
+ title: d.text().min(1),
619
662
  completed: d.boolean().default(false),
620
663
  createdAt: d.timestamp().default('now').readOnly(),
621
664
  updatedAt: d.timestamp().autoUpdate().readOnly(),
@@ -625,14 +668,16 @@ export const tasksModel = d.model(tasksTable);
625
668
  `;
626
669
  }
627
670
  /**
628
- * src/api/db.ts — createSqliteAdapter with autoApply migrations
671
+ * src/api/db.ts — createDb with local SQLite and autoApply migrations
629
672
  */
630
673
  export function dbTemplate() {
631
- return `import { createSqliteAdapter } from 'vertz/db/sqlite';
632
- import { tasksTable } from './schema';
674
+ return `import { createDb } from 'vertz/db';
675
+ import { tasksModel } from './schema';
633
676
 
634
- export const db = await createSqliteAdapter({
635
- schema: tasksTable,
677
+ export const db = createDb({
678
+ models: { tasks: tasksModel },
679
+ dialect: 'sqlite',
680
+ path: '.vertz/data/app.db',
636
681
  migrations: { autoApply: true },
637
682
  });
638
683
  `;
@@ -705,11 +750,11 @@ export function App() {
705
750
  return (
706
751
  <div data-testid="app-root">
707
752
  <ThemeProvider theme="light">
708
- <div class={styles.shell}>
709
- <header class={styles.header}>
710
- <div class={styles.title}>My Vertz App</div>
753
+ <div className={styles.shell}>
754
+ <header className={styles.header}>
755
+ <div className={styles.title}>My Vertz App</div>
711
756
  </header>
712
- <main class={styles.main}>
757
+ <main className={styles.main}>
713
758
  <HomePage />
714
759
  </main>
715
760
  </div>
@@ -735,22 +780,27 @@ mount(App, {
735
780
  `;
736
781
  }
737
782
  /**
738
- * src/styles/theme.ts — configureThemeBase from @vertz/theme-shadcn/base
783
+ * src/styles/theme.ts — configureTheme from @vertz/theme-shadcn
739
784
  */
740
785
  export function themeTemplate() {
741
- return `import { configureThemeBase } from '@vertz/theme-shadcn/base';
786
+ return `import { configureTheme } from '@vertz/theme-shadcn';
787
+ import { registerTheme } from 'vertz/ui';
742
788
 
743
- const { theme, globals } = configureThemeBase({
789
+ const config = configureTheme({
744
790
  palette: 'zinc',
745
791
  radius: 'md',
746
792
  });
747
793
 
748
- export const appTheme = theme;
749
- export const themeGlobals = globals;
794
+ registerTheme(config);
795
+
796
+ export const appTheme = config.theme;
797
+ export const themeGlobals = config.globals;
750
798
  `;
751
799
  }
752
800
  /**
753
- * src/pages/home.tsx — task list + create form with query + css
801
+ * src/pages/home.tsx — full CRUD task list with form, checkbox toggle,
802
+ * delete confirmation dialog, and animated list transitions.
803
+ * Demonstrates theme components (Button, Input, AlertDialog) over raw HTML.
754
804
  */
755
805
  export function homePageTemplate() {
756
806
  return `import {
@@ -765,9 +815,11 @@ export function homePageTemplate() {
765
815
  queryMatch,
766
816
  slideInFromTop,
767
817
  } from 'vertz/ui';
818
+ import { Button } from '@vertz/ui/components';
819
+ import { AlertDialog } from '@vertz/ui/components';
768
820
  import { api } from '../client';
769
821
 
770
- // Inject global CSS for list item enter/exit animations
822
+ // Global CSS for list item enter/exit animations
771
823
  void globalCss({
772
824
  '[data-presence="enter"]': {
773
825
  animation: \`\${slideInFromTop} \${ANIMATION_DURATION} \${ANIMATION_EASING}\`,
@@ -778,31 +830,23 @@ void globalCss({
778
830
  },
779
831
  });
780
832
 
781
- const pageStyles = css({
833
+ const styles = css({
782
834
  container: ['py:2', 'w:full'],
783
835
  heading: ['font:xl', 'font:bold', 'text:foreground', 'mb:4'],
784
- form: ['flex', 'gap:2', 'items:start', 'mb:6'],
836
+ form: ['flex', 'items:start', 'gap:2', 'mb:6'],
785
837
  inputWrap: ['flex-1'],
786
838
  input: [
787
839
  'w:full',
840
+ 'h:10',
788
841
  'px:3',
789
- 'py:2',
790
842
  'rounded:md',
791
843
  'border:1',
792
844
  'border:border',
793
845
  'bg:background',
794
846
  'text:foreground',
847
+ 'text:sm',
795
848
  ],
796
849
  fieldError: ['text:destructive', 'font:xs', 'mt:1'],
797
- button: [
798
- 'px:4',
799
- 'py:2',
800
- 'rounded:md',
801
- 'bg:primary',
802
- 'text:primary-foreground',
803
- 'font:medium',
804
- 'cursor:pointer',
805
- ],
806
850
  list: ['flex', 'flex-col', 'gap:2'],
807
851
  item: [
808
852
  'flex',
@@ -814,12 +858,63 @@ const pageStyles = css({
814
858
  'border:1',
815
859
  'border:border',
816
860
  'bg:card',
861
+ 'hover:bg:accent',
862
+ 'transition:colors',
817
863
  ],
864
+ checkbox: ['w:4', 'h:4', 'cursor:pointer', 'rounded:sm'],
865
+ label: ['flex-1', 'text:sm', 'text:foreground'],
866
+ labelDone: ['flex-1', 'text:sm', 'text:muted-foreground', 'decoration:line-through'],
818
867
  loading: ['text:muted-foreground'],
819
868
  error: ['text:destructive'],
820
869
  empty: ['text:muted-foreground', 'text:center', 'py:8'],
870
+ count: ['text:xs', 'text:muted-foreground', 'mt:4'],
821
871
  });
822
872
 
873
+ interface TaskItemProps {
874
+ id: string;
875
+ title: string;
876
+ completed: boolean;
877
+ }
878
+
879
+ function TaskItem({ id, title, completed }: TaskItemProps) {
880
+ const handleToggle = async () => {
881
+ await api.tasks.update(id, { completed: !completed });
882
+ };
883
+
884
+ const handleDelete = async () => {
885
+ await api.tasks.delete(id);
886
+ };
887
+
888
+ return (
889
+ <div className={styles.item}>
890
+ <input
891
+ type="checkbox"
892
+ className={styles.checkbox}
893
+ checked={completed}
894
+ onChange={handleToggle}
895
+ />
896
+ <span className={completed ? styles.labelDone : styles.label}>
897
+ {title}
898
+ </span>
899
+ <AlertDialog onAction={handleDelete}>
900
+ <AlertDialog.Trigger>
901
+ <Button intent="ghost" size="sm">Delete</Button>
902
+ </AlertDialog.Trigger>
903
+ <AlertDialog.Content>
904
+ <AlertDialog.Title>Delete task?</AlertDialog.Title>
905
+ <AlertDialog.Description>
906
+ This action cannot be undone.
907
+ </AlertDialog.Description>
908
+ <AlertDialog.Footer>
909
+ <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
910
+ <AlertDialog.Action>Delete</AlertDialog.Action>
911
+ </AlertDialog.Footer>
912
+ </AlertDialog.Content>
913
+ </AlertDialog>
914
+ </div>
915
+ );
916
+ }
917
+
823
918
  export function HomePage() {
824
919
  const tasksQuery = query(api.tasks.list());
825
920
 
@@ -828,61 +923,64 @@ export function HomePage() {
828
923
  });
829
924
 
830
925
  return (
831
- <div class={pageStyles.container} data-testid="home-page">
832
- <h1 class={pageStyles.heading}>Tasks</h1>
926
+ <div className={styles.container} data-testid="home-page">
927
+ <h1 className={styles.heading}>Tasks</h1>
833
928
 
834
929
  <form
835
- class={pageStyles.form}
930
+ className={styles.form}
836
931
  action={taskForm.action}
837
932
  method={taskForm.method}
838
933
  onSubmit={taskForm.onSubmit}
839
934
  >
840
- <div class={pageStyles.inputWrap}>
935
+ <div className={styles.inputWrap}>
841
936
  <input
842
937
  name={taskForm.fields.title}
843
- class={pageStyles.input}
938
+ className={styles.input}
844
939
  placeholder="What needs to be done?"
845
940
  />
846
- <span class={pageStyles.fieldError}>
941
+ <span className={styles.fieldError}>
847
942
  {taskForm.title.error}
848
943
  </span>
849
944
  </div>
850
- <button
851
- type="submit"
852
- class={pageStyles.button}
853
- disabled={taskForm.submitting}
854
- >
945
+ <Button type="submit" disabled={taskForm.submitting}>
855
946
  {taskForm.submitting.value ? 'Adding...' : 'Add'}
856
- </button>
947
+ </Button>
857
948
  </form>
858
949
 
859
950
  {queryMatch(tasksQuery, {
860
951
  loading: () => (
861
- <div class={pageStyles.loading}>Loading tasks...</div>
952
+ <div className={styles.loading}>Loading tasks...</div>
862
953
  ),
863
954
  error: (err) => (
864
- <div class={pageStyles.error}>
955
+ <div className={styles.error}>
865
956
  {err instanceof Error ? err.message : String(err)}
866
957
  </div>
867
958
  ),
868
959
  data: (response) => (
869
960
  <>
870
961
  {response.items.length === 0 && (
871
- <div class={pageStyles.empty}>
962
+ <div className={styles.empty}>
872
963
  No tasks yet. Add one above!
873
964
  </div>
874
965
  )}
875
- <div data-testid="task-list" class={pageStyles.list}>
966
+ <div data-testid="task-list" className={styles.list}>
876
967
  <ListTransition
877
968
  each={response.items}
878
969
  keyFn={(task) => task.id}
879
970
  children={(task) => (
880
- <div class={pageStyles.item}>
881
- <span>{task.title}</span>
882
- </div>
971
+ <TaskItem
972
+ id={task.id}
973
+ title={task.title}
974
+ completed={task.completed}
975
+ />
883
976
  )}
884
977
  />
885
978
  </div>
979
+ {response.items.length > 0 && (
980
+ <div className={styles.count}>
981
+ {response.items.filter((t) => !t.completed).length} remaining
982
+ </div>
983
+ )}
886
984
  </>
887
985
  ),
888
986
  })}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/create-vertz-app",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Create a new Vertz application",