@vertz/create-vertz-app 0.2.20 → 0.2.21

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,CAuQlD;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,CAYtC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CA4LzC"}
@@ -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,85 @@ 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.
332
335
 
333
- ### \`css()\` for scoped styles
336
+ ### Using Components
334
337
 
335
338
  \`\`\`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
- });
339
+ import { themeComponents } from '../styles/theme';
340
+
341
+ const { Button, Input } = themeComponents;
342
+ const { AlertDialog } = themeComponents.primitives;
343
+
344
+ // RIGHT — use theme components
345
+ <Button intent="primary" size="md">Submit</Button>
346
+ <Input placeholder="Enter text" />
347
+
348
+ // WRONG — raw HTML with manual styles
349
+ <button className={button({ intent: 'primary', size: 'md' })}>Submit</button>
350
+ <input className={inputStyles.base} placeholder="Enter text" />
351
+ \`\`\`
352
+
353
+ ### Available Components
354
+
355
+ **Direct** (from \`themeComponents\`): \`Button\`, \`Input\`, \`Label\`, \`Badge\`, \`Textarea\`,
356
+ \`Card\` suite, \`Table\` suite, \`Avatar\` suite, \`FormGroup\` suite
341
357
 
342
- return <div class={styles.container}>...</div>;
358
+ **Primitives** (from \`themeComponents.primitives\`): \`AlertDialog\`, \`Dialog\`, \`Tabs\`,
359
+ \`Select\`, \`DropdownMenu\`, \`Popover\`, \`Sheet\`, \`Tooltip\`, \`Accordion\`
360
+ — all with sub-components (\`.Trigger\`, \`.Content\`, \`.Footer\`, etc.)
361
+
362
+ ## Dialogs
363
+
364
+ ### Composable \`<AlertDialog>\` for inline confirmations
365
+
366
+ \`\`\`tsx
367
+ const { Button } = themeComponents;
368
+ const { AlertDialog } = themeComponents.primitives;
369
+
370
+ <AlertDialog>
371
+ <AlertDialog.Trigger>
372
+ <Button intent="danger" size="sm">Delete</Button>
373
+ </AlertDialog.Trigger>
374
+ <AlertDialog.Content>
375
+ <AlertDialog.Title>Delete task?</AlertDialog.Title>
376
+ <AlertDialog.Description>This action cannot be undone.</AlertDialog.Description>
377
+ <AlertDialog.Footer>
378
+ <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
379
+ <AlertDialog.Action onClick={handleDelete}>Delete</AlertDialog.Action>
380
+ </AlertDialog.Footer>
381
+ </AlertDialog.Content>
382
+ </AlertDialog>
343
383
  \`\`\`
344
384
 
345
- ### \`variants()\` for parameterized styles
385
+ ### \`useDialogStack()\` for imperative/stacked dialogs
386
+
387
+ Use when you need promise-based results or dialogs opened from event handlers:
346
388
 
347
389
  \`\`\`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' },
390
+ import { useDialogStack } from 'vertz/ui';
391
+
392
+ const dialogs = useDialogStack();
393
+ const confirmed = await dialogs.open(ConfirmDialog, { message: 'Delete?' });
394
+ if (confirmed) handleDelete();
395
+ \`\`\`
396
+
397
+ ## Styling
398
+
399
+ ### \`css()\` for layout and custom styles
400
+
401
+ Use \`css()\` for layout-specific styles that don't correspond to a theme component:
402
+
403
+ \`\`\`tsx
404
+ const styles = css({
405
+ container: ['flex', 'flex-col', 'gap:4', 'p:6'],
406
+ heading: ['font:xl', 'font:bold', 'text:foreground'],
362
407
  });
363
408
 
364
- <button class={button({ intent: 'danger', size: 'sm' })}>Delete</button>
409
+ return <div className={styles.container}>...</div>;
365
410
  \`\`\`
366
411
 
367
412
  ### Style Tokens
@@ -615,7 +660,7 @@ export function schemaTemplate() {
615
660
 
616
661
  export const tasksTable = d.table('tasks', {
617
662
  id: d.uuid().primary(),
618
- title: d.text(),
663
+ title: d.text().min(1),
619
664
  completed: d.boolean().default(false),
620
665
  createdAt: d.timestamp().default('now').readOnly(),
621
666
  updatedAt: d.timestamp().autoUpdate().readOnly(),
@@ -625,14 +670,16 @@ export const tasksModel = d.model(tasksTable);
625
670
  `;
626
671
  }
627
672
  /**
628
- * src/api/db.ts — createSqliteAdapter with autoApply migrations
673
+ * src/api/db.ts — createDb with local SQLite and autoApply migrations
629
674
  */
630
675
  export function dbTemplate() {
631
- return `import { createSqliteAdapter } from 'vertz/db/sqlite';
632
- import { tasksTable } from './schema';
676
+ return `import { createDb } from 'vertz/db';
677
+ import { tasksModel } from './schema';
633
678
 
634
- export const db = await createSqliteAdapter({
635
- schema: tasksTable,
679
+ export const db = createDb({
680
+ models: { tasks: tasksModel },
681
+ dialect: 'sqlite',
682
+ path: '.vertz/data/app.db',
636
683
  migrations: { autoApply: true },
637
684
  });
638
685
  `;
@@ -705,11 +752,11 @@ export function App() {
705
752
  return (
706
753
  <div data-testid="app-root">
707
754
  <ThemeProvider theme="light">
708
- <div class={styles.shell}>
709
- <header class={styles.header}>
710
- <div class={styles.title}>My Vertz App</div>
755
+ <div className={styles.shell}>
756
+ <header className={styles.header}>
757
+ <div className={styles.title}>My Vertz App</div>
711
758
  </header>
712
- <main class={styles.main}>
759
+ <main className={styles.main}>
713
760
  <HomePage />
714
761
  </main>
715
762
  </div>
@@ -735,22 +782,25 @@ mount(App, {
735
782
  `;
736
783
  }
737
784
  /**
738
- * src/styles/theme.ts — configureThemeBase from @vertz/theme-shadcn/base
785
+ * src/styles/theme.ts — configureTheme from @vertz/theme-shadcn
739
786
  */
740
787
  export function themeTemplate() {
741
- return `import { configureThemeBase } from '@vertz/theme-shadcn/base';
788
+ return `import { configureTheme } from '@vertz/theme-shadcn';
742
789
 
743
- const { theme, globals } = configureThemeBase({
790
+ const { theme, globals, components } = configureTheme({
744
791
  palette: 'zinc',
745
792
  radius: 'md',
746
793
  });
747
794
 
748
795
  export const appTheme = theme;
749
796
  export const themeGlobals = globals;
797
+ export const themeComponents = components;
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 {
@@ -766,8 +816,12 @@ export function homePageTemplate() {
766
816
  slideInFromTop,
767
817
  } from 'vertz/ui';
768
818
  import { api } from '../client';
819
+ import { themeComponents } from '../styles/theme';
769
820
 
770
- // Inject global CSS for list item enter/exit animations
821
+ const { Button } = themeComponents;
822
+ const { AlertDialog } = themeComponents.primitives;
823
+
824
+ // Global CSS for list item enter/exit animations
771
825
  void globalCss({
772
826
  '[data-presence="enter"]': {
773
827
  animation: \`\${slideInFromTop} \${ANIMATION_DURATION} \${ANIMATION_EASING}\`,
@@ -778,31 +832,23 @@ void globalCss({
778
832
  },
779
833
  });
780
834
 
781
- const pageStyles = css({
835
+ const styles = css({
782
836
  container: ['py:2', 'w:full'],
783
837
  heading: ['font:xl', 'font:bold', 'text:foreground', 'mb:4'],
784
- form: ['flex', 'gap:2', 'items:start', 'mb:6'],
838
+ form: ['flex', 'items:start', 'gap:2', 'mb:6'],
785
839
  inputWrap: ['flex-1'],
786
840
  input: [
787
841
  'w:full',
842
+ 'h:10',
788
843
  'px:3',
789
- 'py:2',
790
844
  'rounded:md',
791
845
  'border:1',
792
846
  'border:border',
793
847
  'bg:background',
794
848
  'text:foreground',
849
+ 'text:sm',
795
850
  ],
796
851
  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
852
  list: ['flex', 'flex-col', 'gap:2'],
807
853
  item: [
808
854
  'flex',
@@ -814,12 +860,63 @@ const pageStyles = css({
814
860
  'border:1',
815
861
  'border:border',
816
862
  'bg:card',
863
+ 'hover:bg:accent',
864
+ 'transition:colors',
817
865
  ],
866
+ checkbox: ['w:4', 'h:4', 'cursor:pointer', 'rounded:sm'],
867
+ label: ['flex-1', 'text:sm', 'text:foreground'],
868
+ labelDone: ['flex-1', 'text:sm', 'text:muted-foreground', 'decoration:line-through'],
818
869
  loading: ['text:muted-foreground'],
819
870
  error: ['text:destructive'],
820
871
  empty: ['text:muted-foreground', 'text:center', 'py:8'],
872
+ count: ['text:xs', 'text:muted-foreground', 'mt:4'],
821
873
  });
822
874
 
875
+ interface TaskItemProps {
876
+ id: string;
877
+ title: string;
878
+ completed: boolean;
879
+ }
880
+
881
+ function TaskItem({ id, title, completed }: TaskItemProps) {
882
+ const handleToggle = async () => {
883
+ await api.tasks.update(id, { completed: !completed });
884
+ };
885
+
886
+ const handleDelete = async () => {
887
+ await api.tasks.delete(id);
888
+ };
889
+
890
+ return (
891
+ <div className={styles.item}>
892
+ <input
893
+ type="checkbox"
894
+ className={styles.checkbox}
895
+ checked={completed}
896
+ onChange={handleToggle}
897
+ />
898
+ <span className={completed ? styles.labelDone : styles.label}>
899
+ {title}
900
+ </span>
901
+ <AlertDialog onAction={handleDelete}>
902
+ <AlertDialog.Trigger>
903
+ <Button intent="ghost" size="sm">Delete</Button>
904
+ </AlertDialog.Trigger>
905
+ <AlertDialog.Content>
906
+ <AlertDialog.Title>Delete task?</AlertDialog.Title>
907
+ <AlertDialog.Description>
908
+ This action cannot be undone.
909
+ </AlertDialog.Description>
910
+ <AlertDialog.Footer>
911
+ <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
912
+ <AlertDialog.Action>Delete</AlertDialog.Action>
913
+ </AlertDialog.Footer>
914
+ </AlertDialog.Content>
915
+ </AlertDialog>
916
+ </div>
917
+ );
918
+ }
919
+
823
920
  export function HomePage() {
824
921
  const tasksQuery = query(api.tasks.list());
825
922
 
@@ -828,61 +925,64 @@ export function HomePage() {
828
925
  });
829
926
 
830
927
  return (
831
- <div class={pageStyles.container} data-testid="home-page">
832
- <h1 class={pageStyles.heading}>Tasks</h1>
928
+ <div className={styles.container} data-testid="home-page">
929
+ <h1 className={styles.heading}>Tasks</h1>
833
930
 
834
931
  <form
835
- class={pageStyles.form}
932
+ className={styles.form}
836
933
  action={taskForm.action}
837
934
  method={taskForm.method}
838
935
  onSubmit={taskForm.onSubmit}
839
936
  >
840
- <div class={pageStyles.inputWrap}>
937
+ <div className={styles.inputWrap}>
841
938
  <input
842
939
  name={taskForm.fields.title}
843
- class={pageStyles.input}
940
+ className={styles.input}
844
941
  placeholder="What needs to be done?"
845
942
  />
846
- <span class={pageStyles.fieldError}>
943
+ <span className={styles.fieldError}>
847
944
  {taskForm.title.error}
848
945
  </span>
849
946
  </div>
850
- <button
851
- type="submit"
852
- class={pageStyles.button}
853
- disabled={taskForm.submitting}
854
- >
947
+ <Button type="submit" disabled={taskForm.submitting}>
855
948
  {taskForm.submitting.value ? 'Adding...' : 'Add'}
856
- </button>
949
+ </Button>
857
950
  </form>
858
951
 
859
952
  {queryMatch(tasksQuery, {
860
953
  loading: () => (
861
- <div class={pageStyles.loading}>Loading tasks...</div>
954
+ <div className={styles.loading}>Loading tasks...</div>
862
955
  ),
863
956
  error: (err) => (
864
- <div class={pageStyles.error}>
957
+ <div className={styles.error}>
865
958
  {err instanceof Error ? err.message : String(err)}
866
959
  </div>
867
960
  ),
868
961
  data: (response) => (
869
962
  <>
870
963
  {response.items.length === 0 && (
871
- <div class={pageStyles.empty}>
964
+ <div className={styles.empty}>
872
965
  No tasks yet. Add one above!
873
966
  </div>
874
967
  )}
875
- <div data-testid="task-list" class={pageStyles.list}>
968
+ <div data-testid="task-list" className={styles.list}>
876
969
  <ListTransition
877
970
  each={response.items}
878
971
  keyFn={(task) => task.id}
879
972
  children={(task) => (
880
- <div class={pageStyles.item}>
881
- <span>{task.title}</span>
882
- </div>
973
+ <TaskItem
974
+ id={task.id}
975
+ title={task.title}
976
+ completed={task.completed}
977
+ />
883
978
  )}
884
979
  />
885
980
  </div>
981
+ {response.items.length > 0 && (
982
+ <div className={styles.count}>
983
+ {response.items.filter((t) => !t.completed).length} remaining
984
+ </div>
985
+ )}
886
986
  </>
887
987
  ),
888
988
  })}
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.21",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Create a new Vertz application",