create-z3 0.0.56 → 0.0.57

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/dist/index.js CHANGED
@@ -1817,6 +1817,75 @@ async function replacePlaceholder(filePath, placeholder, content, options) {
1817
1817
  }
1818
1818
  await fs3.writeFile(filePath, updatedLines.join("\n"), "utf-8");
1819
1819
  }
1820
+ function generateOrganizationsPlugin() {
1821
+ return `organization({
1822
+ roles: {
1823
+ admin: {
1824
+ permissions: ["organization:read", "organization:write", "member:write"]
1825
+ },
1826
+ member: {
1827
+ permissions: ["organization:read"]
1828
+ }
1829
+ },
1830
+ adminRole: "admin"
1831
+ }),`;
1832
+ }
1833
+ function generateOrganizationsSchema() {
1834
+ return `// Organization tables
1835
+ [TABLE_SLUG_ORGANIZATIONS]: defineTable({
1836
+ name: v.string(),
1837
+ slug: v.string(),
1838
+ metadata: v.optional(v.any()),
1839
+ logo: v.optional(v.string()),
1840
+ createdAt: v.number(),
1841
+ updatedAt: v.number(),
1842
+ })
1843
+ .index("by_slug", ["slug"]),
1844
+
1845
+ [TABLE_SLUG_ORGANIZATION_MEMBERS]: defineTable({
1846
+ userId: v.id(TABLE_SLUG_USERS),
1847
+ organizationId: v.id(TABLE_SLUG_ORGANIZATIONS),
1848
+ role: v.string(),
1849
+ createdAt: v.number(),
1850
+ updatedAt: v.number(),
1851
+ })
1852
+ .index("by_user", ["userId"])
1853
+ .index("by_organization", ["organizationId"])
1854
+ .index("by_user_organization", ["userId", "organizationId"]),`;
1855
+ }
1856
+ function generateUserOrganizationFields() {
1857
+ return `activeOrganizationId: v.optional(v.id(TABLE_SLUG_ORGANIZATIONS)),`;
1858
+ }
1859
+ function generateOrganizationRoles() {
1860
+ return `
1861
+ export const ORGANIZATION_ROLES = {
1862
+ admin: "admin",
1863
+ member: "member",
1864
+ } as const;
1865
+ export type OrganizationRole = (typeof ORGANIZATION_ROLES)[keyof typeof ORGANIZATION_ROLES];`;
1866
+ }
1867
+ function generateOrganizationTableConstants() {
1868
+ return `export const TABLE_SLUG_ORGANIZATIONS = "organization" as const;
1869
+ export const TABLE_SLUG_ORGANIZATION_MEMBERS = "organization_member" as const;`;
1870
+ }
1871
+ function generateOrganizationTableImports() {
1872
+ return `,
1873
+ TABLE_SLUG_ORGANIZATIONS,
1874
+ TABLE_SLUG_ORGANIZATION_MEMBERS`;
1875
+ }
1876
+ function generateUserWithOrganizationType() {
1877
+ return `import { OrganizationRole } from './constants/auth';
1878
+
1879
+ export type UserWithOrganization = User & {
1880
+ organization?: {
1881
+ id: string;
1882
+ name: string;
1883
+ slug: string;
1884
+ role: OrganizationRole;
1885
+ metadata?: any;
1886
+ } | null;
1887
+ };`;
1888
+ }
1820
1889
  function generateCredentialsValue(enabled) {
1821
1890
  return `credentials={${enabled}}`;
1822
1891
  }
@@ -2196,6 +2265,18 @@ Make sure you are authenticated with GitHub CLI (run "gh auth login")`
2196
2265
  oauthUISpinner.fail("Failed to configure OAuth UI");
2197
2266
  throw error;
2198
2267
  }
2268
+ const multitenancySpinner = ora("Configuring multitenancy...").start();
2269
+ try {
2270
+ await this.updateMultitenancyConfig(options.multitenancy);
2271
+ if (options.multitenancy) {
2272
+ multitenancySpinner.succeed("Multitenancy configuration updated");
2273
+ } else {
2274
+ multitenancySpinner.succeed("Multitenancy placeholders cleaned up");
2275
+ }
2276
+ } catch (error) {
2277
+ multitenancySpinner.fail("Failed to configure multitenancy");
2278
+ throw error;
2279
+ }
2199
2280
  const envSpinner = ora("Updating .env.example...").start();
2200
2281
  try {
2201
2282
  await this.updateEnvExample(options.oauthProviders);
@@ -2389,6 +2470,59 @@ var TanStackInstaller = class extends FrameworkInstaller {
2389
2470
  runtimeMapping
2390
2471
  );
2391
2472
  }
2473
+ /**
2474
+ * Configure multitenancy with organizations
2475
+ * Updates auth plugins, schema, and user types for organization support
2476
+ * Target files:
2477
+ * - convex/auth/plugins/index.ts - Add organization plugin
2478
+ * - convex/schema.ts - Add organization and membership tables
2479
+ * - src/db/constants/auth.ts - Add organization roles
2480
+ * - src/db/types.ts - Update user type with organization
2481
+ *
2482
+ * @param enabled - Whether multitenancy is enabled
2483
+ */
2484
+ async updateMultitenancyConfig(enabled) {
2485
+ const pluginsFilePath = join2(this.targetPath, "convex/auth/plugins/index.ts");
2486
+ await replacePlaceholder(
2487
+ pluginsFilePath,
2488
+ "// {{ORGANIZATIONS_PLUGIN}}",
2489
+ enabled ? generateOrganizationsPlugin() : ""
2490
+ );
2491
+ const schemaFilePath = join2(this.targetPath, "convex/schema.ts");
2492
+ await replacePlaceholder(
2493
+ schemaFilePath,
2494
+ "// {{ORGANIZATION_TABLE_IMPORTS}}",
2495
+ enabled ? generateOrganizationTableImports() : ""
2496
+ );
2497
+ await replacePlaceholder(
2498
+ schemaFilePath,
2499
+ "// {{ORGANIZATIONS_SCHEMA}}",
2500
+ enabled ? generateOrganizationsSchema() : ""
2501
+ );
2502
+ await replacePlaceholder(
2503
+ schemaFilePath,
2504
+ "// {{USER_ORGANIZATION_FIELDS}}",
2505
+ enabled ? generateUserOrganizationFields() : ""
2506
+ );
2507
+ const constantsFilePath = join2(this.targetPath, "src/db/constants/auth.ts");
2508
+ await replacePlaceholder(
2509
+ constantsFilePath,
2510
+ "// {{ORGANIZATION_ROLES}}",
2511
+ enabled ? generateOrganizationRoles() : ""
2512
+ );
2513
+ const constantsIndexFilePath = join2(this.targetPath, "src/db/constants/index.ts");
2514
+ await replacePlaceholder(
2515
+ constantsIndexFilePath,
2516
+ "// {{ORGANIZATION_TABLES}}",
2517
+ enabled ? generateOrganizationTableConstants() : ""
2518
+ );
2519
+ const typesFilePath = join2(this.targetPath, "src/db/types.ts");
2520
+ await replacePlaceholder(
2521
+ typesFilePath,
2522
+ "// {{USER_WITH_ORGANIZATION_TYPE}}",
2523
+ enabled ? generateUserWithOrganizationType() : ""
2524
+ );
2525
+ }
2392
2526
  };
2393
2527
 
2394
2528
  // src/installers/nextjs.ts
@@ -2522,6 +2656,59 @@ var NextJSInstaller = class extends FrameworkInstaller {
2522
2656
  runtimeMapping
2523
2657
  );
2524
2658
  }
2659
+ /**
2660
+ * Configure multitenancy with organizations
2661
+ * Updates auth plugins, schema, and user types for organization support
2662
+ * Target files:
2663
+ * - convex/auth/plugins/index.ts - Add organization plugin
2664
+ * - convex/schema.ts - Add organization and membership tables
2665
+ * - src/db/constants/auth.ts - Add organization roles
2666
+ * - src/db/types.ts - Update user type with organization
2667
+ *
2668
+ * @param enabled - Whether multitenancy is enabled
2669
+ */
2670
+ async updateMultitenancyConfig(enabled) {
2671
+ const pluginsFilePath = join3(this.targetPath, "convex/auth/plugins/index.ts");
2672
+ await replacePlaceholder(
2673
+ pluginsFilePath,
2674
+ "// {{ORGANIZATIONS_PLUGIN}}",
2675
+ enabled ? generateOrganizationsPlugin() : ""
2676
+ );
2677
+ const schemaFilePath = join3(this.targetPath, "convex/schema.ts");
2678
+ await replacePlaceholder(
2679
+ schemaFilePath,
2680
+ "// {{ORGANIZATION_TABLE_IMPORTS}}",
2681
+ enabled ? generateOrganizationTableImports() : ""
2682
+ );
2683
+ await replacePlaceholder(
2684
+ schemaFilePath,
2685
+ "// {{ORGANIZATIONS_SCHEMA}}",
2686
+ enabled ? generateOrganizationsSchema() : ""
2687
+ );
2688
+ await replacePlaceholder(
2689
+ schemaFilePath,
2690
+ "// {{USER_ORGANIZATION_FIELDS}}",
2691
+ enabled ? generateUserOrganizationFields() : ""
2692
+ );
2693
+ const constantsFilePath = join3(this.targetPath, "src/db/constants/auth.ts");
2694
+ await replacePlaceholder(
2695
+ constantsFilePath,
2696
+ "// {{ORGANIZATION_ROLES}}",
2697
+ enabled ? generateOrganizationRoles() : ""
2698
+ );
2699
+ const constantsIndexFilePath = join3(this.targetPath, "src/db/constants/index.ts");
2700
+ await replacePlaceholder(
2701
+ constantsIndexFilePath,
2702
+ "// {{ORGANIZATION_TABLES}}",
2703
+ enabled ? generateOrganizationTableConstants() : ""
2704
+ );
2705
+ const typesFilePath = join3(this.targetPath, "src/db/types.ts");
2706
+ await replacePlaceholder(
2707
+ typesFilePath,
2708
+ "// {{USER_WITH_ORGANIZATION_TYPE}}",
2709
+ enabled ? generateUserWithOrganizationType() : ""
2710
+ );
2711
+ }
2525
2712
  };
2526
2713
 
2527
2714
  // src/index.ts
@@ -2645,6 +2832,21 @@ program.name("create-z3").version(packageJson.version).description("CLI for scaf
2645
2832
  console.log(chalk2.yellow(" Your app will have no user authentication."));
2646
2833
  console.log();
2647
2834
  }
2835
+ let multitenancy = false;
2836
+ if (emailPassword || oauthProviders.length > 0) {
2837
+ multitenancy = await confirm({
2838
+ message: "Enable multitenancy with organizations?",
2839
+ default: false
2840
+ });
2841
+ if (multitenancy) {
2842
+ console.log();
2843
+ console.log(chalk2.cyan("\u{1F3E2} Organizations enabled:"));
2844
+ console.log(chalk2.dim(" \u2022 Users can belong to multiple organizations"));
2845
+ console.log(chalk2.dim(" \u2022 Role-based permissions per organization"));
2846
+ console.log(chalk2.dim(" \u2022 Admin users can manage organization members"));
2847
+ console.log();
2848
+ }
2849
+ }
2648
2850
  console.log();
2649
2851
  console.log(chalk2.dim("\u{1F4A1} TweakCN themes: Visit https://tweakcn.com/themes/[theme-id]"));
2650
2852
  console.log(chalk2.dim(' Click "Code" button and copy the CSS.'));
@@ -2688,6 +2890,7 @@ program.name("create-z3").version(packageJson.version).description("CLI for scaf
2688
2890
  framework,
2689
2891
  emailPasswordAuth: emailPassword,
2690
2892
  oauthProviders,
2893
+ multitenancy,
2691
2894
  tweakcnTheme,
2692
2895
  initGit,
2693
2896
  installDependencies
@@ -2733,6 +2936,11 @@ program.name("create-z3").version(packageJson.version).description("CLI for scaf
2733
2936
  } else {
2734
2937
  console.log(chalk2.dim("Authentication: None selected"));
2735
2938
  }
2939
+ if (multitenancy) {
2940
+ console.log("Organizations: Enabled");
2941
+ } else {
2942
+ console.log("Organizations: Disabled");
2943
+ }
2736
2944
  if (tweakcnTheme) {
2737
2945
  console.log("Theme: Custom TweakCN theme");
2738
2946
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-z3",
3
- "version": "0.0.56",
3
+ "version": "0.0.57",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding Z3 Stack applications (TanStack/Next.js + Convex + Better Auth)",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  import { nextCookies } from "better-auth/next-js"
2
- import { admin } from "better-auth/plugins"
2
+ import { admin, organization } from "better-auth/plugins"
3
3
  import { apiKey } from "@better-auth/api-key"
4
4
  import { convex } from "@convex-dev/better-auth/plugins"
5
5
  import { USER_ROLES } from "~/db/constants"
@@ -14,6 +14,7 @@ export const createPlugins = () => [
14
14
  adminRoles: [USER_ROLES.admin],
15
15
  defaultRole: USER_ROLES.user,
16
16
  }),
17
+ // {{ORGANIZATIONS_PLUGIN}}
17
18
  apiKey(),
18
19
  nextCookies(),
19
20
  convex({ authConfig }),
@@ -1,7 +1,15 @@
1
1
  import { defineSchema, defineTable } from "convex/server";
2
2
  import { v } from "convex/values"
3
3
 
4
- import { TABLE_SLUG_ACCOUNTS, TABLE_SLUG_API_KEYS, TABLE_SLUG_JWKS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS, TABLE_SLUG_VERIFICATIONS } from "~/db/constants";
4
+ import {
5
+ TABLE_SLUG_ACCOUNTS,
6
+ TABLE_SLUG_API_KEYS,
7
+ TABLE_SLUG_JWKS,
8
+ TABLE_SLUG_SESSIONS,
9
+ TABLE_SLUG_USERS,
10
+ TABLE_SLUG_VERIFICATIONS
11
+ // {{ORGANIZATION_TABLE_IMPORTS}}
12
+ } from "~/db/constants";
5
13
 
6
14
  export default defineSchema({
7
15
  // Better Auth component tables (type definitions only - actual tables are in component)
@@ -23,7 +31,8 @@ export default defineSchema({
23
31
  roles: v.array(v.string()), // our multi-role field via additionalFields
24
32
  twoFactorEnabled: v.optional(v.union(v.null(), v.boolean())),
25
33
  updatedAt: v.number(),
26
- userId: v.optional(v.union(v.null(), v.string()))
34
+ userId: v.optional(v.union(v.null(), v.string())),
35
+ // {{USER_ORGANIZATION_FIELDS}}
27
36
  })
28
37
  .index("by_email", ["email"]),
29
38
 
@@ -98,4 +107,6 @@ export default defineSchema({
98
107
  })
99
108
  .index("by_referenceId", ["referenceId"])
100
109
  .index("by_key", ["key"]),
110
+
111
+ // {{ORGANIZATIONS_SCHEMA}}
101
112
  })
@@ -4,3 +4,5 @@ export const USER_ROLES = {
4
4
  } as const;
5
5
  export type UserRole = (typeof USER_ROLES)[keyof typeof USER_ROLES];
6
6
 
7
+ // {{ORGANIZATION_ROLES}}
8
+
@@ -8,6 +8,8 @@ export const TABLE_SLUG_VERIFICATIONS = "verification" as const;
8
8
  export const TABLE_SLUG_JWKS = "jwks" as const;
9
9
  export const TABLE_SLUG_API_KEYS = "apikey" as const;
10
10
 
11
+ // {{ORGANIZATION_TABLES}}
12
+
11
13
  export const COLLECTION_SLUG_MEDIA = "media" as const;
12
14
 
13
15
  export const AUTH_PROVIDERS = {
@@ -3,3 +3,5 @@ import { TABLE_SLUG_USERS } from "./constants";
3
3
 
4
4
  export type User = Doc<typeof TABLE_SLUG_USERS>
5
5
  export type UserID = Id<typeof TABLE_SLUG_USERS>
6
+
7
+ // {{USER_WITH_ORGANIZATION_TYPE}}
@@ -1,4 +1,4 @@
1
- import { admin } from "better-auth/plugins"
1
+ import { admin, organization } from "better-auth/plugins"
2
2
  import { apiKey } from "@better-auth/api-key"
3
3
  import { convex } from "@convex-dev/better-auth/plugins"
4
4
  import authConfig from "@convex/auth.config"
@@ -13,6 +13,7 @@ export const createPlugins = () => [
13
13
  adminRoles: [USER_ROLES.admin],
14
14
  defaultRole: USER_ROLES.user
15
15
  }),
16
+ // {{ORGANIZATIONS_PLUGIN}}
16
17
  apiKey(),
17
18
  convex({ authConfig }),
18
19
  ]
@@ -7,7 +7,8 @@ import {
7
7
  TABLE_SLUG_JWKS,
8
8
  TABLE_SLUG_SESSIONS,
9
9
  TABLE_SLUG_USERS,
10
- TABLE_SLUG_VERIFICATIONS,
10
+ TABLE_SLUG_VERIFICATIONS
11
+ // {{ORGANIZATION_TABLE_IMPORTS}}
11
12
  } from "~/db/constants";
12
13
 
13
14
  export default defineSchema({
@@ -31,6 +32,7 @@ export default defineSchema({
31
32
  banReason: v.optional(v.string()), // admin plugin
32
33
  role: v.optional(v.string()), // admin plugin — single string in BA 1.5
33
34
  roles: v.array(v.string()), // our multi-role field via additionalFields
35
+ // {{USER_ORGANIZATION_FIELDS}}
34
36
  })
35
37
  .index("by_email", ["email"]),
36
38
 
@@ -105,4 +107,6 @@ export default defineSchema({
105
107
  })
106
108
  .index("by_referenceId", ["referenceId"])
107
109
  .index("by_key", ["key"]),
110
+
111
+ // {{ORGANIZATIONS_SCHEMA}}
108
112
  })
@@ -38,4 +38,6 @@ export const AUTH_PROVIDERS = {
38
38
  } as const;
39
39
  export type AuthProvider = (typeof AUTH_PROVIDERS)[keyof typeof AUTH_PROVIDERS];
40
40
 
41
+ // {{ORGANIZATION_ROLES}}
42
+
41
43
 
@@ -7,3 +7,5 @@ export const TABLE_SLUG_SESSIONS = "session" as const;
7
7
  export const TABLE_SLUG_VERIFICATIONS = "verification" as const;
8
8
  export const TABLE_SLUG_JWKS = "jwks" as const;
9
9
  export const TABLE_SLUG_API_KEYS = "apikey" as const;
10
+
11
+ // {{ORGANIZATION_TABLES}}
@@ -3,3 +3,5 @@ import { TABLE_SLUG_USERS } from "./constants";
3
3
 
4
4
  export type User = Doc<typeof TABLE_SLUG_USERS>
5
5
  export type UserID = Id<typeof TABLE_SLUG_USERS>
6
+
7
+ // {{USER_WITH_ORGANIZATION_TYPE}}