cronus-ui 0.6.3 → 0.6.4

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.3` here), not
52
+ - The default registry is pinned to the CLI package version (`v0.6.4` 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.3/registry"
83
+ "registry": "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.4/registry"
84
84
  }
85
85
  ```
86
86
 
@@ -37,10 +37,15 @@ export declare function goldPathLayout(config: CronusUIConfig): GoldPathLayout;
37
37
  export declare function patchChromeSource(source: string, workspaceImport: string, inviteImport: string, sessionImport?: string): string | undefined;
38
38
  /** Insert ItemsPanel into a generated home page. Returns undefined when there is no main. */
39
39
  export declare function patchHomePageSource(source: string, itemsImport: string): string | undefined;
40
+ /**
41
+ * Replace the catalog TeamBlock on a generated /team page with MembersPanel.
42
+ * Idempotent. Returns undefined when there is no TeamBlock and no MembersPanel.
43
+ */
44
+ export declare function patchTeamPageSource(source: string, membersImport: string): string | undefined;
40
45
  /**
41
46
  * Write sqlite + Drizzle + Better-Auth files into a composed saas/admin app.
42
- * Overwrites lib/auth-adapter.ts and lib/items.ts always. Patches the shell
43
- * home page only when compose wrote it this run.
47
+ * Overwrites lib/auth-adapter.ts, lib/items.ts, and lib/members.ts always.
48
+ * Patches the shell home and /team pages only when compose wrote them this run.
44
49
  */
45
50
  export declare function applyGoldPath(options: ApplyGoldPathOptions): Promise<ApplyGoldPathResult>;
46
51
  //# sourceMappingURL=gold-path.d.ts.map
@@ -82,6 +82,13 @@ function homePageRel(appDir, generatedFiles) {
82
82
  const any = generatedFiles.find((f) => /(^|\/)\(shell\)\/page\.tsx$/.test(posix(f)));
83
83
  return any !== undefined ? posix(any) : undefined;
84
84
  }
85
+ function teamPageRel(appDir, generatedFiles) {
86
+ const match = generatedFiles.find((f) => posix(f) === `${appDir}/(shell)/team/page.tsx`);
87
+ if (match !== undefined)
88
+ return posix(match);
89
+ const any = generatedFiles.find((f) => /(^|\/)\(shell\)\/team\/page\.tsx$/.test(posix(f)));
90
+ return any !== undefined ? posix(any) : undefined;
91
+ }
85
92
  function drizzleConfigSource(dbDir) {
86
93
  return `import { mkdirSync } from "node:fs";
87
94
  import { dirname } from "node:path";
@@ -585,6 +592,156 @@ export function ItemsView({
585
592
  }
586
593
  `;
587
594
  }
595
+ function membersActionsSource(authImport) {
596
+ return `"use server";
597
+
598
+ import { and, asc, eq } from "drizzle-orm";
599
+ import { headers } from "next/headers";
600
+ import { db } from "@/db";
601
+ import { member, organization, user } from "@/db/schema";
602
+ import { auth } from ${JSON.stringify(authImport)};
603
+
604
+ async function activeWorkspaceId(): Promise<string | null> {
605
+ const session = await auth.api.getSession({ headers: await headers() });
606
+ const userId = session?.user?.id;
607
+ if (!userId) return null;
608
+ const hinted = session.session?.activeOrganizationId ?? null;
609
+ if (hinted) {
610
+ const [membership] = await db
611
+ .select({ organizationId: member.organizationId })
612
+ .from(member)
613
+ .where(and(eq(member.userId, userId), eq(member.organizationId, hinted)))
614
+ .limit(1);
615
+ if (membership?.organizationId) return membership.organizationId;
616
+ }
617
+ const [row] = await db
618
+ .select({ organizationId: member.organizationId })
619
+ .from(member)
620
+ .where(eq(member.userId, userId))
621
+ .limit(1);
622
+ return row?.organizationId ?? null;
623
+ }
624
+
625
+ export async function loadMembers(): Promise<{
626
+ workspace: string;
627
+ rows: { id: string; name: string; email: string; image: string | null; role: string }[];
628
+ }> {
629
+ const orgId = await activeWorkspaceId();
630
+ if (!orgId) return { workspace: "no workspace", rows: [] };
631
+ const [org] = await db.select().from(organization).where(eq(organization.id, orgId)).limit(1);
632
+ const rows = await db
633
+ .select({
634
+ id: member.id,
635
+ name: user.name,
636
+ email: user.email,
637
+ image: user.image,
638
+ role: member.role,
639
+ })
640
+ .from(member)
641
+ .innerJoin(user, eq(member.userId, user.id))
642
+ .where(eq(member.organizationId, orgId))
643
+ .orderBy(asc(member.createdAt));
644
+ return { workspace: org?.name ?? "no workspace", rows };
645
+ }
646
+ `;
647
+ }
648
+ function membersPanelSource(actionsImport, viewImport) {
649
+ return `import { loadMembers } from ${JSON.stringify(actionsImport)};
650
+ import { MembersView } from ${JSON.stringify(viewImport)};
651
+
652
+ export async function MembersPanel() {
653
+ const data = await loadMembers();
654
+ return <MembersView workspace={data.workspace} rows={data.rows} />;
655
+ }
656
+ `;
657
+ }
658
+ function membersViewSource(inviteImport) {
659
+ return `"use client";
660
+
661
+ import { Avatar, AvatarFallback, AvatarImage, Badge, Button } from "@cronus-ui/ui";
662
+ import { InviteMember } from ${JSON.stringify(inviteImport)};
663
+
664
+ function initialsOf(name: string, email: string): string {
665
+ const parts = name.trim().split(/\\s+/).filter(Boolean);
666
+ if (parts.length >= 2) {
667
+ return \`\${parts[0]?.[0] ?? ""}\${parts[1]?.[0] ?? ""}\`.toUpperCase();
668
+ }
669
+ if (parts[0]?.[0]) return parts[0][0].toUpperCase();
670
+ return email.slice(0, 2).toUpperCase();
671
+ }
672
+
673
+ function roleLabel(role: string): string {
674
+ if (role === "owner") return "Owner";
675
+ if (role === "admin") return "Admin";
676
+ if (role === "member") return "Member";
677
+ return role;
678
+ }
679
+
680
+ function roleVariant(role: string): "primary" | "info" | "secondary" {
681
+ if (role === "owner") return "primary";
682
+ if (role === "admin") return "info";
683
+ return "secondary";
684
+ }
685
+
686
+ export function MembersView({
687
+ workspace,
688
+ rows,
689
+ }: {
690
+ workspace: string;
691
+ rows: { id: string; name: string; email: string; image: string | null; role: string }[];
692
+ }) {
693
+ const count = String(rows.length);
694
+ return (
695
+ <section
696
+ data-slot="members-panel"
697
+ aria-labelledby="members-heading"
698
+ className="mx-auto w-full max-w-xl px-6 py-6"
699
+ >
700
+ <div className="flex items-end justify-between gap-3">
701
+ <div className="min-w-0">
702
+ <h1 id="members-heading" className="text-sm font-semibold text-fg">
703
+ Team
704
+ </h1>
705
+ <p className="mt-1 text-sm text-fg-tertiary">
706
+ {workspace} · {count} members
707
+ </p>
708
+ </div>
709
+ <InviteMember
710
+ trigger={
711
+ <Button type="button" size="sm">
712
+ Invite
713
+ </Button>
714
+ }
715
+ />
716
+ </div>
717
+ {rows.length === 0 ? (
718
+ <p className="mt-4 text-sm text-fg-tertiary">No members yet.</p>
719
+ ) : (
720
+ <ul className="mt-4">
721
+ {rows.map((row) => (
722
+ <li
723
+ key={row.id}
724
+ data-slot="member"
725
+ className="flex items-center gap-3 border-t border-border py-3"
726
+ >
727
+ <Avatar className="size-8">
728
+ {row.image ? <AvatarImage src={row.image} alt={row.name} /> : null}
729
+ <AvatarFallback>{initialsOf(row.name, row.email)}</AvatarFallback>
730
+ </Avatar>
731
+ <div className="flex min-w-0 flex-1 flex-col">
732
+ <span className="truncate text-sm text-fg">{row.name}</span>
733
+ <span className="truncate text-sm text-fg-tertiary">{row.email}</span>
734
+ </div>
735
+ <Badge variant={roleVariant(row.role)}>{roleLabel(row.role)}</Badge>
736
+ </li>
737
+ ))}
738
+ </ul>
739
+ )}
740
+ </section>
741
+ );
742
+ }
743
+ `;
744
+ }
588
745
  function workspaceMenuSource(authClientImport) {
589
746
  return `"use client";
590
747
 
@@ -846,6 +1003,25 @@ export function patchHomePageSource(source, itemsImport) {
846
1003
  }
847
1004
  return out;
848
1005
  }
1006
+ /**
1007
+ * Replace the catalog TeamBlock on a generated /team page with MembersPanel.
1008
+ * Idempotent. Returns undefined when there is no TeamBlock and no MembersPanel.
1009
+ */
1010
+ export function patchTeamPageSource(source, membersImport) {
1011
+ const hasPanel = /<MembersPanel\s*\/>/.test(source);
1012
+ const hasTeam = /<TeamBlock\s*\/>/.test(source);
1013
+ if (!hasPanel && !hasTeam)
1014
+ return undefined;
1015
+ if (hasPanel)
1016
+ return source;
1017
+ let out = source;
1018
+ const importLine = `import { MembersPanel } from ${JSON.stringify(membersImport)};`;
1019
+ out = insertImport(out, importLine);
1020
+ out = out.replace(/import \{ TeamBlock \} from "[^"]+";\n/, "");
1021
+ out = out.replace(/export default(?! async) function/, "export default async function");
1022
+ out = out.replace(/<TeamBlock\s*\/>/, "<MembersPanel />");
1023
+ return out;
1024
+ }
849
1025
  function mergePackageJson(raw) {
850
1026
  const pkg = JSON.parse(raw);
851
1027
  const dependencies = { ...(pkg.dependencies ?? {}) };
@@ -915,8 +1091,8 @@ async function writeRel(targetDir, rel, content, overwrite, always, written, ski
915
1091
  }
916
1092
  /**
917
1093
  * Write sqlite + Drizzle + Better-Auth files into a composed saas/admin app.
918
- * Overwrites lib/auth-adapter.ts and lib/items.ts always. Patches the shell
919
- * home page only when compose wrote it this run.
1094
+ * Overwrites lib/auth-adapter.ts, lib/items.ts, and lib/members.ts always.
1095
+ * Patches the shell home and /team pages only when compose wrote them this run.
920
1096
  */
921
1097
  export async function applyGoldPath(options) {
922
1098
  const { targetDir, config, generatedFiles, overwrite } = options;
@@ -932,6 +1108,9 @@ export async function applyGoldPath(options) {
932
1108
  const itemsActionsImport = `${config.aliases.lib}/items`;
933
1109
  const itemsViewImport = "@/components/items-view";
934
1110
  const itemsImport = "@/components/items-panel";
1111
+ const membersActionsImport = `${config.aliases.lib}/members`;
1112
+ const membersViewImport = "@/components/members-view";
1113
+ const membersImport = "@/components/members-panel";
935
1114
  const workspaceImport = "@/components/workspace-menu";
936
1115
  const inviteImport = "@/components/invite-member";
937
1116
  const sessionImport = "@/components/session-user";
@@ -965,6 +1144,21 @@ export async function applyGoldPath(options) {
965
1144
  content: itemsViewSource(itemsActionsImport),
966
1145
  always: true,
967
1146
  },
1147
+ {
1148
+ rel: `${layout.libDir}/members.ts`,
1149
+ content: membersActionsSource(authImport),
1150
+ always: true,
1151
+ },
1152
+ {
1153
+ rel: `${layout.componentsDir}/members-panel.tsx`,
1154
+ content: membersPanelSource(membersActionsImport, membersViewImport),
1155
+ always: true,
1156
+ },
1157
+ {
1158
+ rel: `${layout.componentsDir}/members-view.tsx`,
1159
+ content: membersViewSource(inviteImport),
1160
+ always: true,
1161
+ },
968
1162
  {
969
1163
  rel: `${layout.componentsDir}/workspace-menu.tsx`,
970
1164
  content: workspaceMenuSource(authClientImport),
@@ -1004,6 +1198,22 @@ export async function applyGoldPath(options) {
1004
1198
  }
1005
1199
  }
1006
1200
  }
1201
+ const teamRel = teamPageRel(appDir, generatedFiles);
1202
+ if (teamRel !== undefined) {
1203
+ const dest = resolveSafeDest(targetDir, ".", teamRel);
1204
+ if (existsSync(dest)) {
1205
+ const current = await readFile(dest, "utf8");
1206
+ const patched = patchTeamPageSource(current, membersImport);
1207
+ if (patched !== undefined && patched !== current) {
1208
+ await writeFileEnsured(dest, patched);
1209
+ const templateName = options.templateName;
1210
+ if (templateName !== undefined) {
1211
+ const snapDest = resolveSafeDest(targetDir, baseSnapshotDir(templateName), teamRel);
1212
+ await writeFileEnsured(snapDest, patched);
1213
+ }
1214
+ }
1215
+ }
1216
+ }
1007
1217
  const homeRel = homePageRel(appDir, generatedFiles);
1008
1218
  if (homeRel !== undefined) {
1009
1219
  const dest = resolveSafeDest(targetDir, ".", homeRel);
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.3";
3
- export declare const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.3/registry";
2
+ export declare const CLI_VERSION = "0.6.4";
3
+ export declare const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.4/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.3";
5
+ export const CLI_VERSION = "0.6.4";
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.3",
3
+ "version": "0.6.4",
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.3",
57
+ "@cronus-ui/ai-kit": "0.6.4",
58
58
  "commander": "^15.0.0",
59
59
  "picocolors": "^1.1.1"
60
60
  },