cronus-ui 0.6.2 → 0.6.3

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/README.md CHANGED
@@ -49,7 +49,7 @@ Notes that match the commander surface:
49
49
  `packages/cli/scripts/build-registry.ts` — each item carries its source, its npm
50
50
  `dependencies`, and its `registryDependencies` (other components it imports),
51
51
  derived by parsing imports.
52
- - The default registry is pinned to the CLI package version (`v0.6.2` here), not
52
+ - The default registry is pinned to the CLI package version (`v0.6.3` here), not
53
53
  mutable `main`, so a published CLI reads the registry snapshot it was released
54
54
  with. Use `-r, --registry ./registry` when testing local registry changes before
55
55
  a release tag exists.
@@ -80,7 +80,7 @@ Notes that match the commander surface:
80
80
  "lib": "lib",
81
81
  "blocks": "components/blocks"
82
82
  },
83
- "registry": "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.2/registry"
83
+ "registry": "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.3/registry"
84
84
  }
85
85
  ```
86
86
 
@@ -39,8 +39,8 @@ export declare function patchChromeSource(source: string, workspaceImport: strin
39
39
  export declare function patchHomePageSource(source: string, itemsImport: string): string | undefined;
40
40
  /**
41
41
  * Write sqlite + Drizzle + Better-Auth files into a composed saas/admin app.
42
- * Overwrites lib/auth-adapter.ts always (replaces the demo adapter). Patches
43
- * the shell home page only when compose wrote it this run.
42
+ * Overwrites lib/auth-adapter.ts and lib/items.ts always. Patches the shell
43
+ * home page only when compose wrote it this run.
44
44
  */
45
45
  export declare function applyGoldPath(options: ApplyGoldPathOptions): Promise<ApplyGoldPathResult>;
46
46
  //# sourceMappingURL=gold-path.d.ts.map
@@ -38,6 +38,7 @@ const ENV_VARS = {
38
38
  };
39
39
  const GITIGNORE_ENTRIES = ["*.db", "data/", "drizzle/"];
40
40
  const DATABASE_URL_FALLBACK = "file:./data/app.db";
41
+ const TITLE_MAX = 200;
41
42
  export function goldPathLayout(config) {
42
43
  const libDir = posix(config.paths.lib);
43
44
  const uiDir = posix(config.paths.ui);
@@ -432,37 +433,154 @@ export const config = {
432
433
  };
433
434
  `;
434
435
  }
435
- function itemsPanelSource(authImport) {
436
- return `import { eq } from "drizzle-orm";
436
+ function itemsActionsSource(authImport) {
437
+ return `"use server";
438
+
439
+ import { and, desc, eq } from "drizzle-orm";
440
+ import { revalidatePath } from "next/cache";
437
441
  import { headers } from "next/headers";
438
442
  import { db } from "@/db";
439
443
  import { items, member, organization } from "@/db/schema";
440
444
  import { auth } from ${JSON.stringify(authImport)};
441
445
 
442
- export async function ItemsPanel() {
446
+ const TITLE_MAX = ${TITLE_MAX};
447
+
448
+ async function activeWorkspaceId(): Promise<string | null> {
443
449
  const session = await auth.api.getSession({ headers: await headers() });
444
- let orgId = session?.session?.activeOrganizationId ?? null;
445
- if (!orgId && session?.user?.id) {
446
- const [row] = await db
450
+ const userId = session?.user?.id;
451
+ if (!userId) return null;
452
+ const hinted = session.session?.activeOrganizationId ?? null;
453
+ if (hinted) {
454
+ const [membership] = await db
447
455
  .select({ organizationId: member.organizationId })
448
456
  .from(member)
449
- .where(eq(member.userId, session.user.id))
457
+ .where(and(eq(member.userId, userId), eq(member.organizationId, hinted)))
450
458
  .limit(1);
451
- orgId = row?.organizationId ?? null;
459
+ if (membership?.organizationId) return membership.organizationId;
452
460
  }
453
- const org = orgId
454
- ? (await db.select().from(organization).where(eq(organization.id, orgId)).limit(1))[0]
455
- : undefined;
456
- const rows = orgId
457
- ? await db.select().from(items).where(eq(items.workspaceId, orgId))
458
- : [];
461
+ const [row] = await db
462
+ .select({ organizationId: member.organizationId })
463
+ .from(member)
464
+ .where(eq(member.userId, userId))
465
+ .limit(1);
466
+ return row?.organizationId ?? null;
467
+ }
468
+
469
+ export async function loadItems(): Promise<{
470
+ email: string;
471
+ workspace: string;
472
+ rows: { id: number; title: string }[];
473
+ }> {
474
+ const session = await auth.api.getSession({ headers: await headers() });
459
475
  const email = session?.user?.email ?? "signed out";
460
- const workspace = org?.name ?? "no workspace";
476
+ const orgId = await activeWorkspaceId();
477
+ if (!orgId) return { email, workspace: "no workspace", rows: [] };
478
+ const [org] = await db.select().from(organization).where(eq(organization.id, orgId)).limit(1);
479
+ const rows = await db
480
+ .select({ id: items.id, title: items.title })
481
+ .from(items)
482
+ .where(eq(items.workspaceId, orgId))
483
+ .orderBy(desc(items.id));
484
+ return { email, workspace: org?.name ?? "no workspace", rows };
485
+ }
486
+
487
+ export async function createItem(formData: FormData) {
488
+ const orgId = await activeWorkspaceId();
489
+ if (!orgId) return;
490
+ const title = String(formData.get("title") ?? "")
491
+ .trim()
492
+ .slice(0, TITLE_MAX);
493
+ if (!title) return;
494
+ await db.insert(items).values({ title, workspaceId: orgId });
495
+ revalidatePath("/");
496
+ }
497
+
498
+ export async function deleteItem(formData: FormData) {
499
+ const orgId = await activeWorkspaceId();
500
+ if (!orgId) return;
501
+ const id = Number(formData.get("id"));
502
+ if (!Number.isInteger(id) || id < 1) return;
503
+ await db.delete(items).where(and(eq(items.id, id), eq(items.workspaceId, orgId)));
504
+ revalidatePath("/");
505
+ }
506
+ `;
507
+ }
508
+ function itemsPanelSource(actionsImport, viewImport) {
509
+ return `import { loadItems } from ${JSON.stringify(actionsImport)};
510
+ import { ItemsView } from ${JSON.stringify(viewImport)};
511
+
512
+ export async function ItemsPanel() {
513
+ const data = await loadItems();
514
+ return <ItemsView email={data.email} workspace={data.workspace} rows={data.rows} />;
515
+ }
516
+ `;
517
+ }
518
+ function itemsViewSource(actionsImport) {
519
+ return `"use client";
520
+
521
+ import { Button, Input } from "@cronus-ui/ui";
522
+ import { createItem, deleteItem } from ${JSON.stringify(actionsImport)};
523
+
524
+ export function ItemsView({
525
+ email,
526
+ workspace,
527
+ rows,
528
+ }: {
529
+ email: string;
530
+ workspace: string;
531
+ rows: { id: number; title: string }[];
532
+ }) {
461
533
  const count = String(rows.length);
462
534
  return (
463
- <p className="px-6 pt-6 text-sm text-fg-tertiary">
464
- {email} · {workspace} · {count} items
465
- </p>
535
+ <section
536
+ data-slot="items-panel"
537
+ aria-labelledby="items-heading"
538
+ className="border-b border-border px-6 py-6"
539
+ >
540
+ <h2 id="items-heading" className="text-sm font-semibold text-fg">
541
+ Items
542
+ </h2>
543
+ <p className="mt-1 text-sm text-fg-tertiary">
544
+ {email} · {workspace} · {count} items
545
+ </p>
546
+ <form action={createItem} className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-end">
547
+ <div className="flex min-w-0 flex-1 flex-col gap-2">
548
+ <label htmlFor="item-title" className="text-sm font-medium text-fg">
549
+ Title
550
+ </label>
551
+ <Input
552
+ id="item-title"
553
+ name="title"
554
+ required
555
+ maxLength={${TITLE_MAX}}
556
+ autoComplete="off"
557
+ placeholder="New item"
558
+ />
559
+ </div>
560
+ <Button type="submit">Add</Button>
561
+ </form>
562
+ {rows.length === 0 ? (
563
+ <p className="mt-4 text-sm text-fg-tertiary">No items yet.</p>
564
+ ) : (
565
+ <ul className="mt-4">
566
+ {rows.map((row) => (
567
+ <li
568
+ key={row.id}
569
+ data-slot="item"
570
+ className="flex items-center justify-between gap-3 border-t border-border py-3"
571
+ >
572
+ <span className="min-w-0 truncate text-sm text-fg">{row.title}</span>
573
+ <form action={deleteItem}>
574
+ <input type="hidden" name="id" value={row.id} />
575
+ <Button type="submit" variant="ghost" size="sm" aria-label={"Delete " + row.title}>
576
+ Delete
577
+ </Button>
578
+ </form>
579
+ </li>
580
+ ))}
581
+ </ul>
582
+ )}
583
+ </section>
466
584
  );
467
585
  }
468
586
  `;
@@ -797,8 +915,8 @@ async function writeRel(targetDir, rel, content, overwrite, always, written, ski
797
915
  }
798
916
  /**
799
917
  * Write sqlite + Drizzle + Better-Auth files into a composed saas/admin app.
800
- * Overwrites lib/auth-adapter.ts always (replaces the demo adapter). Patches
801
- * the shell home page only when compose wrote it this run.
918
+ * Overwrites lib/auth-adapter.ts and lib/items.ts always. Patches the shell
919
+ * home page only when compose wrote it this run.
802
920
  */
803
921
  export async function applyGoldPath(options) {
804
922
  const { targetDir, config, generatedFiles, overwrite } = options;
@@ -811,6 +929,8 @@ export async function applyGoldPath(options) {
811
929
  : layout.middlewareRel;
812
930
  const authImport = `${config.aliases.lib}/auth`;
813
931
  const authClientImport = `${config.aliases.lib}/auth-client`;
932
+ const itemsActionsImport = `${config.aliases.lib}/items`;
933
+ const itemsViewImport = "@/components/items-view";
814
934
  const itemsImport = "@/components/items-panel";
815
935
  const workspaceImport = "@/components/workspace-menu";
816
936
  const inviteImport = "@/components/invite-member";
@@ -825,12 +945,26 @@ export async function applyGoldPath(options) {
825
945
  { rel: `${layout.libDir}/auth.ts`, content: authServerSource() },
826
946
  { rel: `${layout.libDir}/auth-client.ts`, content: authClientSource() },
827
947
  { rel: `${layout.libDir}/auth-adapter.ts`, content: authAdapterSource(), always: true },
948
+ {
949
+ rel: `${layout.libDir}/items.ts`,
950
+ content: itemsActionsSource(authImport),
951
+ always: true,
952
+ },
828
953
  {
829
954
  rel: `${appDir}/api/auth/[...all]/route.ts`,
830
955
  content: authRouteSource(authImport),
831
956
  },
832
957
  { rel: middlewareRel, content: middlewareSource() },
833
- { rel: `${layout.componentsDir}/items-panel.tsx`, content: itemsPanelSource(authImport) },
958
+ {
959
+ rel: `${layout.componentsDir}/items-panel.tsx`,
960
+ content: itemsPanelSource(itemsActionsImport, itemsViewImport),
961
+ always: true,
962
+ },
963
+ {
964
+ rel: `${layout.componentsDir}/items-view.tsx`,
965
+ content: itemsViewSource(itemsActionsImport),
966
+ always: true,
967
+ },
834
968
  {
835
969
  rel: `${layout.componentsDir}/workspace-menu.tsx`,
836
970
  content: workspaceMenuSource(authClientImport),
package/dist/config.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export declare const CONFIG_FILE = "cronus-ui.json";
2
- export declare const CLI_VERSION = "0.6.2";
3
- export declare const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.2/registry";
2
+ export declare const CLI_VERSION = "0.6.3";
3
+ export declare const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.3/registry";
4
4
  /** Manifest entry `add`/`upgrade` record per installed registry item. */
5
5
  export interface InstalledRecord {
6
6
  /** Registry release the files came from (git tag without the leading "v"). */
package/dist/config.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  export const CONFIG_FILE = "cronus-ui.json";
5
- export const CLI_VERSION = "0.6.2";
5
+ export const CLI_VERSION = "0.6.3";
6
6
  export const DEFAULT_REGISTRY = `https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v${CLI_VERSION}/registry`;
7
7
  export const DEFAULT_CONFIG = {
8
8
  aliases: { ui: "@/components/ui", lib: "@/lib", blocks: "@/components/blocks" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronus-ui",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "cronus-ui — add Cronus UI components to your project, shadcn-style (copy-paste registry).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,7 +54,7 @@
54
54
  "registry:check": "bun run scripts/check-registry.ts"
55
55
  },
56
56
  "dependencies": {
57
- "@cronus-ui/ai-kit": "0.6.2",
57
+ "@cronus-ui/ai-kit": "0.6.3",
58
58
  "commander": "^15.0.0",
59
59
  "picocolors": "^1.1.1"
60
60
  },