bunkit-cli 1.3.3 → 1.4.0

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.
Files changed (2) hide show
  1. package/dist/index.js +1327 -1051
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14851,39 +14851,89 @@ var init_monorepo = __esm(() => {
14851
14851
  init_fs();
14852
14852
  });
14853
14853
 
14854
+ // ../core/src/presets/custom.ts
14855
+ function getPresetsDir() {
14856
+ return join(process.env.HOME || process.env.USERPROFILE || ".", ".bunkit");
14857
+ }
14858
+ function getPresetsFile() {
14859
+ return join(getPresetsDir(), "presets.json");
14860
+ }
14861
+ async function loadCustomPresets() {
14862
+ try {
14863
+ const presetsDir = getPresetsDir();
14864
+ await ensureDirectory(presetsDir);
14865
+ const content = await readFile(getPresetsFile());
14866
+ return JSON.parse(content);
14867
+ } catch (_error) {
14868
+ return {};
14869
+ }
14870
+ }
14871
+ async function saveCustomPreset(preset) {
14872
+ await ensureDirectory(getPresetsDir());
14873
+ const presets = await loadCustomPresets();
14874
+ const existing = presets[preset.name];
14875
+ presets[preset.name] = {
14876
+ ...preset,
14877
+ createdAt: existing?.createdAt || new Date().toISOString(),
14878
+ updatedAt: new Date().toISOString()
14879
+ };
14880
+ await writeFile(getPresetsFile(), JSON.stringify(presets, null, 2));
14881
+ }
14882
+ async function deleteCustomPreset(name) {
14883
+ const presets = await loadCustomPresets();
14884
+ if (!presets[name]) {
14885
+ return false;
14886
+ }
14887
+ delete presets[name];
14888
+ await writeFile(getPresetsFile(), JSON.stringify(presets, null, 2));
14889
+ return true;
14890
+ }
14891
+ async function getCustomPreset(name) {
14892
+ const presets = await loadCustomPresets();
14893
+ return presets[name] || null;
14894
+ }
14895
+ async function listCustomPresets() {
14896
+ const presets = await loadCustomPresets();
14897
+ return Object.values(presets);
14898
+ }
14899
+ var init_custom2 = __esm(() => {
14900
+ init_dist();
14901
+ init_fs();
14902
+ });
14903
+
14854
14904
  // ../core/src/presets/registry.ts
14855
14905
  class PresetRegistry {
14856
14906
  static aliasMap = null;
14857
14907
  static getAliasMap() {
14858
- if (!this.aliasMap) {
14859
- this.aliasMap = new Map;
14908
+ if (!PresetRegistry.aliasMap) {
14909
+ PresetRegistry.aliasMap = new Map;
14860
14910
  for (const [name, definition] of Object.entries(PRESET_DEFINITIONS)) {
14861
- this.aliasMap.set(name, name);
14911
+ PresetRegistry.aliasMap.set(name, name);
14862
14912
  for (const alias of definition.aliases) {
14863
- this.aliasMap.set(alias, name);
14913
+ PresetRegistry.aliasMap.set(alias, name);
14864
14914
  }
14865
14915
  }
14866
14916
  }
14867
- return this.aliasMap;
14917
+ return PresetRegistry.aliasMap;
14868
14918
  }
14869
14919
  static normalize(input) {
14870
- const map = this.getAliasMap();
14920
+ const map = PresetRegistry.getAliasMap();
14871
14921
  return map.get(input) || null;
14872
14922
  }
14873
14923
  static get(input) {
14874
- const normalized = this.normalize(input);
14924
+ const normalized = PresetRegistry.normalize(input);
14875
14925
  if (!normalized)
14876
14926
  return null;
14877
14927
  return PRESET_DEFINITIONS[normalized];
14878
14928
  }
14879
14929
  static hasCapability(input, capability) {
14880
- const preset = this.get(input);
14930
+ const preset = PresetRegistry.get(input);
14881
14931
  if (!preset)
14882
14932
  return false;
14883
14933
  return preset.capabilities[capability];
14884
14934
  }
14885
14935
  static isMonorepo(input) {
14886
- return this.hasCapability(input, "isMonorepo");
14936
+ return PresetRegistry.hasCapability(input, "isMonorepo");
14887
14937
  }
14888
14938
  static getSelectOptions() {
14889
14939
  return Object.values(PRESET_DEFINITIONS).map((preset) => ({
@@ -14896,10 +14946,10 @@ class PresetRegistry {
14896
14946
  return Object.values(PRESET_DEFINITIONS).filter((preset) => preset.capabilities[capability]);
14897
14947
  }
14898
14948
  static isValid(input) {
14899
- return this.normalize(input) !== null;
14949
+ return PresetRegistry.normalize(input) !== null;
14900
14950
  }
14901
14951
  static getAllValidNames() {
14902
- return Array.from(this.getAliasMap().keys());
14952
+ return Array.from(PresetRegistry.getAliasMap().keys());
14903
14953
  }
14904
14954
  }
14905
14955
  var PRESET_DEFINITIONS;
@@ -15097,14 +15147,14 @@ var init_registry = __esm(() => {
15097
15147
  autoprefixer: "^10.4.23",
15098
15148
  "radix-ui": "^1.4.3",
15099
15149
  "@radix-ui/react-slot": "^1.2.4",
15100
- "@base-ui/react": "^1.0.0",
15101
- shadcn: "^3.6.2",
15150
+ "@base-ui/react": "^1.2.0",
15151
+ shadcn: "^3.8.5",
15102
15152
  "class-variance-authority": "^0.7.1",
15103
15153
  clsx: "^2.1.1",
15104
15154
  "tailwind-merge": "^3.4.0",
15105
15155
  "tw-animate-css": "^1.4.0",
15106
- "@phosphor-icons/react": "^2.1.10",
15107
15156
  "iconoir-react": "^7.11.0",
15157
+ "@phosphor-icons/react": "^2.1.10",
15108
15158
  ultracite: "^6.4.2",
15109
15159
  "@biomejs/biome": "^2.3.10",
15110
15160
  vitest: "^4.0.16",
@@ -15158,14 +15208,14 @@ var init_registry = __esm(() => {
15158
15208
  autoprefixer: "^10.4.23",
15159
15209
  "radix-ui": "^1.4.3",
15160
15210
  "@radix-ui/react-slot": "^1.2.4",
15161
- "@base-ui/react": "^1.0.0",
15162
- shadcn: "^3.6.2",
15211
+ "@base-ui/react": "^1.2.0",
15212
+ shadcn: "^3.8.5",
15163
15213
  "class-variance-authority": "^0.7.1",
15164
15214
  clsx: "^2.1.1",
15165
15215
  "tailwind-merge": "^3.4.0",
15166
15216
  "tw-animate-css": "^1.4.0",
15167
- "@phosphor-icons/react": "^2.1.10",
15168
15217
  "iconoir-react": "^7.11.0",
15218
+ "@phosphor-icons/react": "^2.1.10",
15169
15219
  ultracite: "^6.4.2",
15170
15220
  "@biomejs/biome": "^2.3.10",
15171
15221
  vitest: "^4.0.16",
@@ -15221,14 +15271,14 @@ var init_registry = __esm(() => {
15221
15271
  autoprefixer: "^10.4.23",
15222
15272
  "radix-ui": "^1.4.3",
15223
15273
  "@radix-ui/react-slot": "^1.2.4",
15224
- "@base-ui/react": "^1.0.0",
15225
- shadcn: "^3.6.2",
15274
+ "@base-ui/react": "^1.2.0",
15275
+ shadcn: "^3.8.5",
15226
15276
  "class-variance-authority": "^0.7.1",
15227
15277
  clsx: "^2.1.1",
15228
15278
  "tailwind-merge": "^3.4.0",
15229
15279
  "tw-animate-css": "^1.4.0",
15230
- "@phosphor-icons/react": "^2.1.10",
15231
15280
  "iconoir-react": "^7.11.0",
15281
+ "@phosphor-icons/react": "^2.1.10",
15232
15282
  ultracite: "^6.4.2",
15233
15283
  "@biomejs/biome": "^2.3.10",
15234
15284
  vitest: "^4.0.16",
@@ -15240,60 +15290,10 @@ var init_registry = __esm(() => {
15240
15290
  };
15241
15291
  });
15242
15292
 
15243
- // ../core/src/presets/custom.ts
15244
- function getPresetsDir() {
15245
- return join(process.env.HOME || process.env.USERPROFILE || ".", ".bunkit");
15246
- }
15247
- function getPresetsFile() {
15248
- return join(getPresetsDir(), "presets.json");
15249
- }
15250
- async function loadCustomPresets() {
15251
- try {
15252
- const presetsDir = getPresetsDir();
15253
- await ensureDirectory(presetsDir);
15254
- const content = await readFile(getPresetsFile());
15255
- return JSON.parse(content);
15256
- } catch (_error) {
15257
- return {};
15258
- }
15259
- }
15260
- async function saveCustomPreset(preset) {
15261
- await ensureDirectory(getPresetsDir());
15262
- const presets = await loadCustomPresets();
15263
- const existing = presets[preset.name];
15264
- presets[preset.name] = {
15265
- ...preset,
15266
- createdAt: existing?.createdAt || new Date().toISOString(),
15267
- updatedAt: new Date().toISOString()
15268
- };
15269
- await writeFile(getPresetsFile(), JSON.stringify(presets, null, 2));
15270
- }
15271
- async function deleteCustomPreset(name) {
15272
- const presets = await loadCustomPresets();
15273
- if (!presets[name]) {
15274
- return false;
15275
- }
15276
- delete presets[name];
15277
- await writeFile(getPresetsFile(), JSON.stringify(presets, null, 2));
15278
- return true;
15279
- }
15280
- async function getCustomPreset(name) {
15281
- const presets = await loadCustomPresets();
15282
- return presets[name] || null;
15283
- }
15284
- async function listCustomPresets() {
15285
- const presets = await loadCustomPresets();
15286
- return Object.values(presets);
15287
- }
15288
- var init_custom2 = __esm(() => {
15289
- init_dist();
15290
- init_fs();
15291
- });
15292
-
15293
15293
  // ../core/src/presets/index.ts
15294
15294
  var init_presets = __esm(() => {
15295
- init_registry();
15296
15295
  init_custom2();
15296
+ init_registry();
15297
15297
  });
15298
15298
 
15299
15299
  // ../core/src/project.ts
@@ -15465,6 +15465,7 @@ function createTemplateContext(config) {
15465
15465
  shadcnMenuAccent: config.shadcnMenuAccent,
15466
15466
  shadcnMenuColor: config.shadcnMenuColor,
15467
15467
  shadcnRadius: config.shadcnRadius,
15468
+ shadcnRtl: config.shadcnRtl,
15468
15469
  supabasePreset: config.supabasePreset,
15469
15470
  supabaseFeatures: config.supabaseFeatures,
15470
15471
  supabaseWithDrizzle: config.supabaseWithDrizzle
@@ -19444,6 +19445,17 @@ var init_zod = __esm(() => {
19444
19445
  });
19445
19446
 
19446
19447
  // ../core/src/types.ts
19448
+ function inferShadcnBase(style) {
19449
+ if (style?.startsWith("base-")) {
19450
+ return "base-ui";
19451
+ }
19452
+ return "radix";
19453
+ }
19454
+ function isModernShadcnStyle(style) {
19455
+ if (!style)
19456
+ return false;
19457
+ return style.startsWith("radix-") || style.startsWith("base-");
19458
+ }
19447
19459
  var ProjectConfigSchema, FeatureConfigSchema;
19448
19460
  var init_types2 = __esm(() => {
19449
19461
  init_zod();
@@ -19490,13 +19502,27 @@ var init_types2 = __esm(() => {
19490
19502
  tsStrictness: exports_external.enum(["strict", "moderate", "loose"]).default("strict"),
19491
19503
  uiLibrary: exports_external.enum(["shadcn", "none"]).optional(),
19492
19504
  cssFramework: exports_external.enum(["tailwind", "vanilla", "css-modules"]).optional(),
19493
- shadcnStyle: exports_external.enum(["radix-maia", "radix-vega", "radix-nova", "radix-lyra", "radix-mira", "new-york", "default"]).optional(),
19505
+ shadcnStyle: exports_external.enum([
19506
+ "radix-maia",
19507
+ "radix-vega",
19508
+ "radix-nova",
19509
+ "radix-lyra",
19510
+ "radix-mira",
19511
+ "base-maia",
19512
+ "base-vega",
19513
+ "base-nova",
19514
+ "base-lyra",
19515
+ "base-mira",
19516
+ "new-york",
19517
+ "default"
19518
+ ]).optional(),
19494
19519
  shadcnBase: exports_external.enum(["radix", "base-ui"]).optional(),
19495
19520
  shadcnBaseColor: exports_external.enum(["neutral", "gray", "zinc", "stone", "slate"]).optional(),
19496
19521
  shadcnIconLibrary: exports_external.enum(["phosphor", "lucide", "iconoir"]).optional(),
19497
19522
  shadcnMenuAccent: exports_external.enum(["subtle", "bold"]).optional(),
19498
19523
  shadcnMenuColor: exports_external.enum(["default", "muted"]).optional(),
19499
19524
  shadcnRadius: exports_external.string().optional(),
19525
+ shadcnRtl: exports_external.boolean().optional(),
19500
19526
  testing: exports_external.enum(["bun-test", "vitest", "none"]).default("bun-test"),
19501
19527
  docker: exports_external.boolean().default(false),
19502
19528
  cicd: exports_external.boolean().default(false),
@@ -19571,6 +19597,7 @@ __export(exports_src, {
19571
19597
  logger: () => logger,
19572
19598
  loadCustomPresets: () => loadCustomPresets,
19573
19599
  listCustomPresets: () => listCustomPresets,
19600
+ isModernShadcnStyle: () => isModernShadcnStyle,
19574
19601
  isGitRepository: () => isGitRepository,
19575
19602
  isGitAvailable: () => isGitAvailable,
19576
19603
  isDirectoryEmpty: () => isDirectoryEmpty,
@@ -19578,6 +19605,7 @@ __export(exports_src, {
19578
19605
  installDevDependencies: () => installDevDependencies,
19579
19606
  installDependencies: () => installDependencies,
19580
19607
  initGit: () => initGit,
19608
+ inferShadcnBase: () => inferShadcnBase,
19581
19609
  getWorkspaceName: () => getWorkspaceName,
19582
19610
  getRootPackageJson: () => getRootPackageJson,
19583
19611
  getProjectName: () => getProjectName,
@@ -21850,11 +21878,13 @@ async function writeMonorepoRootPackageJson(projectPath, projectName, catalog, o
21850
21878
  });
21851
21879
  }
21852
21880
  async function writeNextjsAppPackageJson(appPath, scopeName, appName, options = {}) {
21881
+ const iconLibrary = options.shadcnIconLibrary || "iconoir";
21882
+ const iconPackageName = iconLibrary === "iconoir" ? "iconoir-react" : iconLibrary === "phosphor" ? "@phosphor-icons/react" : "lucide-react";
21853
21883
  const dependencies = {
21854
21884
  react: "catalog:",
21855
21885
  "react-dom": "catalog:",
21856
21886
  next: "catalog:",
21857
- "iconoir-react": "catalog:"
21887
+ [iconPackageName]: "catalog:"
21858
21888
  };
21859
21889
  if (options.usesTypes) {
21860
21890
  dependencies[`@${scopeName}/types`] = "workspace:*";
@@ -21916,7 +21946,23 @@ async function writeHonoApiPackageJson(appPath, scopeName, options = {}) {
21916
21946
  }
21917
21947
  });
21918
21948
  }
21919
- async function writeUiPackageJson(packagePath, scopeName) {
21949
+ async function writeUiPackageJson(packagePath, scopeName, options = {}) {
21950
+ const shadcnBase = options.shadcnBase || "radix";
21951
+ const iconLibrary = options.shadcnIconLibrary || "iconoir";
21952
+ const uiFoundationDeps = {};
21953
+ if (shadcnBase === "base-ui") {
21954
+ uiFoundationDeps["@base-ui/react"] = "catalog:";
21955
+ } else {
21956
+ uiFoundationDeps["radix-ui"] = "catalog:";
21957
+ }
21958
+ const iconDeps = {};
21959
+ if (iconLibrary === "iconoir") {
21960
+ iconDeps["iconoir-react"] = "catalog:";
21961
+ } else if (iconLibrary === "phosphor") {
21962
+ iconDeps["@phosphor-icons/react"] = "catalog:";
21963
+ } else {
21964
+ iconDeps["lucide-react"] = "catalog:";
21965
+ }
21920
21966
  await writePackageJson(packagePath, {
21921
21967
  name: `@${scopeName}/ui`,
21922
21968
  version: "0.0.0",
@@ -21936,13 +21982,11 @@ async function writeUiPackageJson(packagePath, scopeName) {
21936
21982
  lint: "tsc --noEmit"
21937
21983
  },
21938
21984
  dependencies: {
21939
- "radix-ui": "catalog:",
21940
- "@radix-ui/react-slot": "catalog:",
21985
+ ...uiFoundationDeps,
21941
21986
  "class-variance-authority": "catalog:",
21942
21987
  clsx: "catalog:",
21943
21988
  "tailwind-merge": "catalog:",
21944
- "@phosphor-icons/react": "catalog:",
21945
- "iconoir-react": "catalog:",
21989
+ ...iconDeps,
21946
21990
  "tw-animate-css": "catalog:",
21947
21991
  shadcn: "catalog:",
21948
21992
  tailwindcss: "catalog:",
@@ -28715,9 +28759,10 @@ function generateModernThemeCSS(baseColor, customRadius) {
28715
28759
  async function setupShadcnWeb(projectPath, context) {
28716
28760
  await ensureDirectory(join(projectPath, "src/components/ui"));
28717
28761
  await ensureDirectory(join(projectPath, "src/lib"));
28718
- const style = context.shadcnStyle || "new-york";
28762
+ const style = context.shadcnStyle || "radix-maia";
28719
28763
  const baseColor = context.shadcnBaseColor || "zinc";
28720
28764
  const radius = context.shadcnRadius || "0.625rem";
28765
+ const iconLibrary = context.shadcnIconLibrary || "iconoir";
28721
28766
  const componentsJson = {
28722
28767
  $schema: "https://ui.shadcn.com/schema.json",
28723
28768
  style,
@@ -28729,7 +28774,7 @@ async function setupShadcnWeb(projectPath, context) {
28729
28774
  baseColor,
28730
28775
  cssVariables: true
28731
28776
  },
28732
- iconLibrary: "lucide",
28777
+ iconLibrary,
28733
28778
  aliases: {
28734
28779
  components: "@/components",
28735
28780
  utils: "@/lib/utils",
@@ -28738,6 +28783,9 @@ async function setupShadcnWeb(projectPath, context) {
28738
28783
  hooks: "@/hooks"
28739
28784
  }
28740
28785
  };
28786
+ if (context.shadcnRtl) {
28787
+ componentsJson.rtl = true;
28788
+ }
28741
28789
  await writeFile(join(projectPath, "components.json"), JSON.stringify(componentsJson, null, 2));
28742
28790
  const utilsContent = `import { clsx, type ClassValue } from 'clsx';
28743
28791
  import { twMerge } from 'tailwind-merge';
@@ -28755,20 +28803,36 @@ export function cn(...inputs: ClassValue[]) {
28755
28803
  if (!packageJson.dependencies) {
28756
28804
  packageJson.dependencies = {};
28757
28805
  }
28758
- packageJson.dependencies["@radix-ui/react-slot"] = "catalog:";
28806
+ const shadcnBase = inferShadcnBase(context.shadcnStyle);
28807
+ if (shadcnBase === "base-ui") {
28808
+ packageJson.dependencies["@base-ui/react"] = "catalog:";
28809
+ } else {
28810
+ packageJson.dependencies["radix-ui"] = "catalog:";
28811
+ }
28759
28812
  packageJson.dependencies["class-variance-authority"] = "catalog:";
28760
28813
  packageJson.dependencies.clsx = "catalog:";
28761
28814
  packageJson.dependencies["tailwind-merge"] = "catalog:";
28762
- packageJson.dependencies["lucide-react"] = "catalog:";
28815
+ const iconPackageName = iconLibrary === "iconoir" ? "iconoir-react" : iconLibrary === "phosphor" ? "@phosphor-icons/react" : "lucide-react";
28816
+ packageJson.dependencies[iconPackageName] = "catalog:";
28763
28817
  if (!packageJson.catalog) {
28764
28818
  packageJson.catalog = {};
28765
28819
  }
28766
- packageJson.catalog["@radix-ui/react-slot"] = "^1.2.4";
28820
+ if (shadcnBase === "base-ui") {
28821
+ packageJson.catalog["@base-ui/react"] = "^1.2.0";
28822
+ } else {
28823
+ packageJson.catalog["radix-ui"] = "^1.4.3";
28824
+ }
28767
28825
  packageJson.catalog["class-variance-authority"] = "^0.7.1";
28768
28826
  packageJson.catalog.clsx = "^2.1.1";
28769
28827
  packageJson.catalog["tailwind-merge"] = "^3.4.0";
28770
- packageJson.catalog["lucide-react"] = "^0.562.0";
28771
28828
  packageJson.catalog["tw-animate-css"] = "^1.2.9";
28829
+ if (iconLibrary === "iconoir") {
28830
+ packageJson.catalog["iconoir-react"] = "^7.11.0";
28831
+ } else if (iconLibrary === "phosphor") {
28832
+ packageJson.catalog["@phosphor-icons/react"] = "^2.1.10";
28833
+ } else {
28834
+ packageJson.catalog["lucide-react"] = "^0.562.0";
28835
+ }
28772
28836
  await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
28773
28837
  const globalsCssPath = join(projectPath, "src/app/globals.css");
28774
28838
  let globalsCss = "";
@@ -28795,9 +28859,11 @@ ${shadcnCss}`;
28795
28859
  async function setupShadcnMonorepo(projectPath, context) {
28796
28860
  const packageName = context.packageName || context.projectName.toLowerCase().replace(/\s+/g, "-");
28797
28861
  const uiPackageName = `@${packageName}/ui`;
28798
- const style = context.shadcnStyle || "new-york";
28862
+ const style = context.shadcnStyle || "radix-maia";
28799
28863
  const baseColor = context.shadcnBaseColor || "zinc";
28800
28864
  const radius = context.shadcnRadius || "0.625rem";
28865
+ const iconLibrary = context.shadcnIconLibrary || "iconoir";
28866
+ const shadcnBase = inferShadcnBase(context.shadcnStyle);
28801
28867
  await ensureDirectory(join(projectPath, "packages/ui/src/components"));
28802
28868
  await ensureDirectory(join(projectPath, "packages/ui/src/lib"));
28803
28869
  await ensureDirectory(join(projectPath, "packages/ui/src/hooks"));
@@ -28819,11 +28885,11 @@ async function setupShadcnMonorepo(projectPath, context) {
28819
28885
  "./components/ui/*": "./src/components/ui/*/index.ts"
28820
28886
  },
28821
28887
  dependencies: {
28822
- "@radix-ui/react-slot": "catalog:",
28888
+ ...shadcnBase === "base-ui" ? { "@base-ui/react": "catalog:" } : { "radix-ui": "catalog:" },
28823
28889
  "class-variance-authority": "catalog:",
28824
28890
  clsx: "catalog:",
28825
28891
  "tailwind-merge": "catalog:",
28826
- "lucide-react": "catalog:",
28892
+ ...iconLibrary === "iconoir" ? { "iconoir-react": "catalog:" } : iconLibrary === "phosphor" ? { "@phosphor-icons/react": "catalog:" } : { "lucide-react": "catalog:" },
28827
28893
  tailwindcss: "catalog:",
28828
28894
  "@tailwindcss/postcss": "catalog:"
28829
28895
  },
@@ -28845,7 +28911,7 @@ async function setupShadcnMonorepo(projectPath, context) {
28845
28911
  baseColor,
28846
28912
  cssVariables: true
28847
28913
  },
28848
- iconLibrary: "lucide",
28914
+ iconLibrary,
28849
28915
  aliases: {
28850
28916
  components: "./src/components",
28851
28917
  ui: "./src/components/ui",
@@ -28854,6 +28920,9 @@ async function setupShadcnMonorepo(projectPath, context) {
28854
28920
  lib: "@/lib"
28855
28921
  }
28856
28922
  };
28923
+ if (context.shadcnRtl) {
28924
+ uiComponentsJson.rtl = true;
28925
+ }
28857
28926
  await writeFile(join(projectPath, "packages/ui/components.json"), JSON.stringify(uiComponentsJson, null, 2));
28858
28927
  const uiUtilsContent = `import { clsx, type ClassValue } from 'clsx';
28859
28928
  import { twMerge } from 'tailwind-merge';
@@ -28943,7 +29012,7 @@ export default config;
28943
29012
  baseColor,
28944
29013
  cssVariables: true
28945
29014
  },
28946
- iconLibrary: "lucide",
29015
+ iconLibrary,
28947
29016
  aliases: {
28948
29017
  components: "@/components",
28949
29018
  hooks: "@/hooks",
@@ -28952,6 +29021,9 @@ export default config;
28952
29021
  ui: "@workspace/ui/components/ui"
28953
29022
  }
28954
29023
  };
29024
+ if (context.shadcnRtl) {
29025
+ appComponentsJson.rtl = true;
29026
+ }
28955
29027
  await writeFile(join(appPath, "components.json"), JSON.stringify(appComponentsJson, null, 2));
28956
29028
  const layoutPath = join(appPath, "src/app/layout.tsx");
28957
29029
  if (await fileExists(layoutPath)) {
@@ -29047,16 +29119,26 @@ export default nextConfig;
29047
29119
  rootPackageJson.catalog = {};
29048
29120
  }
29049
29121
  const shadcnDependencies = {
29050
- "@radix-ui/react-slot": "^1.2.4",
29051
29122
  "class-variance-authority": "^0.7.1",
29052
29123
  clsx: "^2.1.1",
29053
29124
  "tailwind-merge": "^3.4.0",
29054
- "lucide-react": "^0.562.0",
29055
29125
  "tw-animate-css": "^1.2.9",
29056
29126
  "@types/react": "^19.2.7",
29057
29127
  "@types/react-dom": "^19.2.3",
29058
29128
  typescript: "^5.9.3"
29059
29129
  };
29130
+ if (shadcnBase === "base-ui") {
29131
+ shadcnDependencies["@base-ui/react"] = "^1.2.0";
29132
+ } else {
29133
+ shadcnDependencies["radix-ui"] = "^1.4.3";
29134
+ }
29135
+ if (iconLibrary === "iconoir") {
29136
+ shadcnDependencies["iconoir-react"] = "^7.11.0";
29137
+ } else if (iconLibrary === "phosphor") {
29138
+ shadcnDependencies["@phosphor-icons/react"] = "^2.1.10";
29139
+ } else {
29140
+ shadcnDependencies["lucide-react"] = "^0.562.0";
29141
+ }
29060
29142
  Object.assign(rootPackageJson.catalog, shadcnDependencies);
29061
29143
  await writeFile(rootPackageJsonPath, JSON.stringify(rootPackageJson, null, 2));
29062
29144
  await createShadcnDocs(join(projectPath, "packages/ui"), true, context);
@@ -29451,11 +29533,15 @@ async function buildEnterprisePreset(projectPath, context) {
29451
29533
  autoprefixer: "^10.4.23",
29452
29534
  postcss: "^8.5.6",
29453
29535
  "@tailwindcss/postcss": "^4.1.18",
29536
+ "radix-ui": "^1.4.3",
29454
29537
  "@radix-ui/react-slot": "^1.2.4",
29538
+ "@base-ui/react": "^1.2.0",
29539
+ shadcn: "^3.8.5",
29455
29540
  "class-variance-authority": "^0.7.1",
29456
29541
  clsx: "^2.1.1",
29457
29542
  "tailwind-merge": "^3.4.0",
29458
29543
  "iconoir-react": "^7.11.0",
29544
+ "@phosphor-icons/react": "^2.1.10",
29459
29545
  "lucide-react": "^0.562.0",
29460
29546
  "tw-animate-css": "^1.2.9",
29461
29547
  "@biomejs/biome": "^2.3.10",
@@ -29628,9 +29714,11 @@ async function buildEnterprisePreset(projectPath, context) {
29628
29714
  const enterpriseContext = {
29629
29715
  ...context,
29630
29716
  uiLibrary: "shadcn",
29631
- shadcnStyle: context.shadcnStyle || "new-york",
29717
+ shadcnStyle: context.shadcnStyle || "radix-maia",
29632
29718
  shadcnBaseColor: context.shadcnBaseColor || "zinc",
29633
- shadcnRadius: context.shadcnRadius || "0.625rem"
29719
+ shadcnRadius: context.shadcnRadius || "0.625rem",
29720
+ shadcnIconLibrary: context.shadcnIconLibrary || "iconoir",
29721
+ shadcnRtl: context.shadcnRtl
29634
29722
  };
29635
29723
  await setupShadcnMonorepo(projectPath, enterpriseContext);
29636
29724
  }
@@ -29791,11 +29879,15 @@ async function buildFullPreset(projectPath, context) {
29791
29879
  autoprefixer: "^10.4.23",
29792
29880
  postcss: "^8.5.6",
29793
29881
  "@tailwindcss/postcss": "^4.1.18",
29882
+ "radix-ui": "^1.4.3",
29794
29883
  "@radix-ui/react-slot": "^1.2.4",
29884
+ "@base-ui/react": "^1.2.0",
29885
+ shadcn: "^3.8.5",
29795
29886
  "class-variance-authority": "^0.7.1",
29796
29887
  clsx: "^2.1.1",
29797
29888
  "tailwind-merge": "^3.4.0",
29798
29889
  "iconoir-react": "^7.11.0",
29890
+ "@phosphor-icons/react": "^2.1.10",
29799
29891
  "lucide-react": "^0.562.0",
29800
29892
  "tw-animate-css": "^1.2.9",
29801
29893
  ...context.codeQuality === "biome" ? { "@biomejs/biome": "^2.3.10" } : {},
@@ -30589,1046 +30681,1023 @@ networks:
30589
30681
  const fullContext = {
30590
30682
  ...context,
30591
30683
  uiLibrary: "shadcn",
30592
- shadcnStyle: context.shadcnStyle || "new-york",
30684
+ shadcnStyle: context.shadcnStyle || "radix-maia",
30593
30685
  shadcnBaseColor: context.shadcnBaseColor || "zinc",
30594
- shadcnRadius: context.shadcnRadius || "0.625rem"
30686
+ shadcnRadius: context.shadcnRadius || "0.625rem",
30687
+ shadcnIconLibrary: context.shadcnIconLibrary || "iconoir",
30688
+ shadcnRtl: context.shadcnRtl
30595
30689
  };
30596
30690
  await setupShadcnMonorepo(projectPath, fullContext);
30597
30691
  }
30598
30692
  await setupTooling(projectPath, context);
30599
30693
  await setupVSCodeDebug(projectPath, context, "full");
30600
30694
  }
30601
- // ../templates/src/builders/minimal.ts
30695
+ // ../templates/src/builders/full-v2.ts
30602
30696
  init_src();
30603
30697
  init_dist();
30604
- async function buildMinimalPreset(projectPath, context) {
30605
- const indexContent = `console.log('Hello from ${context.projectName}! \uD83C\uDF5E');
30606
-
30607
- // Your code here
30608
- const greet = (name: string): void => {
30609
- console.log(\`Welcome, \${name}!\`);
30610
- };
30698
+ init_package_json();
30611
30699
 
30612
- greet('Bun');
30613
- `;
30614
- await writeFile(join(projectPath, "src/index.ts"), indexContent);
30615
- const tsconfigContent = {
30700
+ // ../templates/src/shared/ui-package.ts
30701
+ init_src();
30702
+ init_dist();
30703
+ init_package_json();
30704
+ async function buildUiPackage(packagesPath, options) {
30705
+ const uiPath = join(packagesPath, "ui");
30706
+ await ensureDirectory(join(uiPath, "src/components"));
30707
+ await ensureDirectory(join(uiPath, "src/hooks"));
30708
+ await ensureDirectory(join(uiPath, "src/lib"));
30709
+ await ensureDirectory(join(uiPath, "src/styles"));
30710
+ const style = options.shadcnStyle || "radix-maia";
30711
+ const useModernStyle = isModernShadcnStyle(style);
30712
+ const shadcnBase = options.shadcnBase || inferShadcnBase(style);
30713
+ const iconLibrary = options.shadcnIconLibrary || "iconoir";
30714
+ await writeUiPackageJson(uiPath, options.scopeName, {
30715
+ shadcnBase,
30716
+ shadcnIconLibrary: iconLibrary
30717
+ });
30718
+ await writeFile(join(uiPath, "tsconfig.json"), JSON.stringify({
30719
+ extends: "../../tooling/typescript/library.json",
30616
30720
  compilerOptions: {
30617
- lib: ["ESNext"],
30618
- target: "ESNext",
30619
- module: "ESNext",
30620
- moduleDetection: "force",
30621
30721
  jsx: "react-jsx",
30622
- allowJs: true,
30623
- moduleResolution: "bundler",
30624
- allowImportingTsExtensions: true,
30625
- verbatimModuleSyntax: true,
30626
- noEmit: true,
30627
- strict: true,
30628
- skipLibCheck: true,
30629
- noFallthroughCasesInSwitch: true,
30630
- noUnusedLocals: false,
30631
- noUnusedParameters: false,
30632
- noPropertyAccessFromIndexSignature: false,
30633
- types: ["bun-types"]
30722
+ rootDir: "./src",
30723
+ outDir: "./dist",
30724
+ baseUrl: ".",
30725
+ paths: {
30726
+ "@/*": ["./src/*"]
30727
+ }
30728
+ },
30729
+ include: ["src/**/*"],
30730
+ exclude: ["node_modules"]
30731
+ }, null, 2));
30732
+ const componentsJson = {
30733
+ $schema: "https://ui.shadcn.com/schema.json",
30734
+ style,
30735
+ rsc: true,
30736
+ tsx: true,
30737
+ tailwind: {
30738
+ config: "",
30739
+ css: "./src/styles/globals.css",
30740
+ baseColor: options.shadcnBaseColor || "zinc",
30741
+ cssVariables: true,
30742
+ prefix: ""
30743
+ },
30744
+ iconLibrary,
30745
+ aliases: {
30746
+ components: "@/components",
30747
+ utils: "@/lib/utils",
30748
+ ui: "@/components/ui",
30749
+ lib: "@/lib",
30750
+ hooks: "@/hooks"
30634
30751
  }
30635
30752
  };
30636
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
30637
- const bunfigContent = generateBunfigContent(context);
30638
- await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
30639
- if (context.codeQuality === "ultracite") {
30640
- await setupUltracite(projectPath, context);
30641
- } else {
30642
- await setupBiome(projectPath, context);
30753
+ if (useModernStyle) {
30754
+ componentsJson.menuColor = options.shadcnMenuColor || "default";
30755
+ componentsJson.menuAccent = options.shadcnMenuAccent || "subtle";
30756
+ componentsJson.registries = {};
30643
30757
  }
30644
- await setupVSCodeDebug(projectPath, context, "minimal");
30645
- await generateMinimalReadme(projectPath, context);
30646
- }
30647
- // ../templates/src/builders/monorepo-bun.ts
30648
- init_src();
30649
- init_dist();
30650
- var databaseSetupMap6 = {
30651
- "postgres-drizzle": setupPostgresDrizzle,
30652
- "postgres-prisma": setupPostgresPrisma,
30653
- "mysql-drizzle": setupMySQLDrizzle,
30654
- "mysql-prisma": setupMySQLPrisma,
30655
- supabase: setupSupabaseOnly,
30656
- "supabase-drizzle": setupSupabaseDrizzle,
30657
- "supabase-prisma": setupSupabasePrisma,
30658
- "sqlite-drizzle": setupSQLiteDrizzle,
30659
- "sqlite-prisma": setupSQLitePrisma,
30660
- none: async () => {}
30661
- };
30662
- async function buildMonorepoBunPreset(projectPath, context) {
30663
- await ensureDirectory(join(projectPath, "apps/web"));
30664
- await ensureDirectory(join(projectPath, "apps/api"));
30665
- await ensureDirectory(join(projectPath, "packages/types"));
30666
- await ensureDirectory(join(projectPath, "packages/utils"));
30667
- if (context.database && context.database !== "none") {
30668
- await ensureDirectory(join(projectPath, "packages/db"));
30758
+ if (options.shadcnRtl) {
30759
+ componentsJson.rtl = true;
30669
30760
  }
30670
- const rootPackageJson = {
30671
- name: `${context.packageName}-monorepo`,
30672
- version: "0.0.0",
30673
- private: true,
30674
- workspaces: ["apps/*", "packages/*"],
30675
- scripts: {
30676
- dev: 'bun run --filter "*" dev',
30677
- build: 'bun run --filter "*" build',
30678
- lint: context.codeQuality === "biome" ? "biome check ." : "ultracite check .",
30679
- format: context.codeQuality === "biome" ? "biome check --write ." : "ultracite fix .",
30680
- test: "bun test",
30681
- "dev:web": 'bun run --filter "@*/web" dev',
30682
- "dev:api": 'bun run --filter "@*/api" dev',
30683
- debug: "bun --inspect apps/api/src/index.ts",
30684
- "debug:brk": "bun --inspect-brk apps/api/src/index.ts",
30685
- "debug:wait": "bun --inspect-wait apps/api/src/index.ts"
30686
- },
30687
- devDependencies: {
30688
- "@types/bun": "latest",
30689
- typescript: "catalog:",
30690
- ...context.codeQuality === "biome" ? { "@biomejs/biome": "catalog:" } : {}
30691
- },
30692
- catalog: {
30693
- react: "^19.2.3",
30694
- "react-dom": "^19.2.3",
30695
- "drizzle-orm": "^0.45.1",
30696
- "drizzle-kit": "^0.31.8",
30697
- postgres: "^3.4.7",
30698
- "@supabase/supabase-js": "^2.88.0",
30699
- "@prisma/client": "^7.2.0",
30700
- prisma: "^7.2.0",
30701
- mysql2: "^3.16.0",
30702
- "better-auth": "^1.4.7",
30703
- "next-auth": "^4.24.13",
30704
- "@auth/drizzle-adapter": "^1.11.1",
30705
- tailwindcss: "^4.1.18",
30706
- autoprefixer: "^10.4.23",
30707
- postcss: "^8.5.6",
30708
- typescript: "^5.9.3",
30709
- "@types/react": "^19.2.7",
30710
- "@types/react-dom": "^19.2.3",
30711
- "@types/node": "^25.0.3"
30712
- }
30713
- };
30714
- await writeFile(join(projectPath, "package.json"), JSON.stringify(rootPackageJson, null, 2));
30715
- const apiPath = join(projectPath, "apps/api");
30716
- await setupBunServeNative(apiPath, context, true);
30717
- const apiPackageJson = {
30718
- name: `@${context.packageName}/api`,
30719
- version: "0.0.0",
30720
- private: true,
30721
- scripts: {
30722
- dev: "bun run --hot src/index.ts",
30723
- start: "bun run src/index.ts",
30724
- debug: "bun --inspect src/index.ts",
30725
- "debug:brk": "bun --inspect-brk src/index.ts",
30726
- "debug:wait": "bun --inspect-wait src/index.ts"
30727
- },
30728
- dependencies: {
30729
- [`@${context.packageName}/types`]: "workspace:*"
30730
- },
30731
- devDependencies: {
30732
- "@types/bun": "latest",
30733
- typescript: "catalog:"
30734
- }
30735
- };
30736
- await writeFile(join(apiPath, "package.json"), JSON.stringify(apiPackageJson, null, 2));
30737
- const webPath = join(projectPath, "apps/web");
30738
- await setupBunFullstack(webPath, context);
30739
- const webPackageJson = {
30740
- name: `@${context.packageName}/web`,
30741
- version: "0.0.0",
30742
- private: true,
30743
- scripts: {
30744
- dev: "bun run --hot src/index.ts",
30745
- build: "bun build src/index.ts --outdir ./dist --target bun",
30746
- start: "bun run dist/index.js",
30747
- debug: "bun --inspect src/index.ts",
30748
- "debug:brk": "bun --inspect-brk src/index.ts",
30749
- "debug:wait": "bun --inspect-wait src/index.ts"
30750
- },
30751
- dependencies: {
30752
- react: "catalog:",
30753
- "react-dom": "catalog:",
30754
- [`@${context.packageName}/types`]: "workspace:*"
30755
- },
30756
- devDependencies: {
30757
- "@types/react": "catalog:",
30758
- "@types/react-dom": "catalog:",
30759
- "@types/bun": "latest",
30760
- typescript: "catalog:"
30761
- }
30762
- };
30763
- await writeFile(join(webPath, "package.json"), JSON.stringify(webPackageJson, null, 2));
30764
- if (context.database && context.database !== "none") {
30765
- await databaseSetupMap6[context.database](join(projectPath, "packages/db"), context, true);
30766
- }
30767
- if (context.redis) {
30768
- await setupRedis(join(projectPath, "packages"), context, true);
30769
- }
30770
- if (context.auth === "better-auth") {
30771
- await setupBetterAuth(join(projectPath, "packages"), context, true);
30772
- } else if (context.auth === "nextauth") {
30773
- await setupNextAuth(join(projectPath, "packages"), context, true);
30774
- }
30775
- if (context.useBunSecrets) {
30776
- await setupBunSecrets(join(projectPath, "packages"), context, true);
30777
- }
30778
- if (context.codeQuality === "biome") {
30779
- await setupBiome(projectPath, context);
30761
+ await writeFile(join(uiPath, "components.json"), JSON.stringify(componentsJson, null, 2));
30762
+ await writeFile(join(uiPath, "postcss.config.mjs"), `export default {
30763
+ plugins: {
30764
+ '@tailwindcss/postcss': {},
30765
+ },
30766
+ };
30767
+ `);
30768
+ const appsToScan = options.appsToScan || ["web", "platform"];
30769
+ const appSourcePaths = appsToScan.map((app) => `@source "../../../apps/${app}/src/**/*.{ts,tsx}";`);
30770
+ const baseColor = options.shadcnBaseColor || "zinc";
30771
+ const customRadius = options.shadcnRadius || "0.625rem";
30772
+ let globalsCss;
30773
+ if (useModernStyle) {
30774
+ const modernThemeCSS = generateModernThemeCSS(baseColor, customRadius);
30775
+ const shadcnImport = shadcnBase === "base-ui" ? "" : `
30776
+ @import "shadcn/tailwind.css";`;
30777
+ globalsCss = `@import "tailwindcss";
30778
+ @import "tw-animate-css";${shadcnImport}
30779
+ ${appSourcePaths.join(`
30780
+ `)}
30781
+ @source "../**/*.{ts,tsx}";
30782
+
30783
+ @custom-variant dark (&:is(.dark *));
30784
+
30785
+ ${modernThemeCSS}
30786
+ `;
30780
30787
  } else {
30781
- await setupUltracite(projectPath, context);
30782
- }
30783
- if (context.docker) {
30784
- await setupDocker(projectPath, context);
30785
- }
30786
- if (context.cicd) {
30787
- await setupGitHubActions(projectPath, context);
30788
+ const theme = themes[baseColor] || themes.zinc;
30789
+ const themeCSS = generateThemeCSS(theme, customRadius);
30790
+ globalsCss = `@import "tailwindcss";
30791
+ ${appSourcePaths.join(`
30792
+ `)}
30793
+ @source "../**/*.{ts,tsx}";
30794
+
30795
+ ${themeCSS}
30796
+ `;
30788
30797
  }
30789
- await setupTooling(projectPath, context);
30790
- await setupVSCodeDebug(projectPath, context, "full");
30791
- const bunfigContent = generateBunfigContent(context);
30792
- await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
30793
- const typesPackageJson = {
30794
- name: `@${context.packageName}/types`,
30795
- version: "0.0.0",
30796
- private: true,
30797
- main: "./index.ts",
30798
- types: "./index.ts",
30799
- scripts: {},
30800
- devDependencies: {
30801
- typescript: "catalog:"
30802
- }
30803
- };
30804
- await ensureDirectory(join(projectPath, "packages/types"));
30805
- await writeFile(join(projectPath, "packages/types/package.json"), JSON.stringify(typesPackageJson, null, 2));
30806
- const typesIndex = `// Shared types across the monorepo
30798
+ await writeFile(join(uiPath, "src/styles/globals.css"), globalsCss);
30799
+ await writeFile(join(uiPath, "src/lib/utils.ts"), `import { clsx, type ClassValue } from 'clsx';
30800
+ import { twMerge } from 'tailwind-merge';
30801
+
30802
+ export function cn(...inputs: ClassValue[]) {
30803
+ return twMerge(clsx(inputs));
30804
+ }
30805
+ `);
30806
+ await writeFile(join(uiPath, "src/components/index.ts"), `/**
30807
+ * UI Components
30808
+ *
30809
+ * Add shadcn/ui components using:
30810
+ * bunx shadcn@latest add button card dialog ...
30811
+ *
30812
+ * Then export them here for use across apps.
30813
+ */
30814
+
30815
+ // Export components as they are added
30816
+ // Example: export { Button } from './ui/button';
30817
+
30818
+ // Empty export to make this a valid ES module
30819
+ export {};
30820
+ `);
30821
+ await writeFile(join(uiPath, "src/hooks/index.ts"), `/**
30822
+ * Shared Hooks
30823
+ *
30824
+ * Custom React hooks shared across apps.
30825
+ */
30826
+
30827
+ // Export hooks as they are added
30828
+ // Example: export { useMediaQuery } from './use-media-query';
30829
+
30830
+ // Empty export to make this a valid ES module
30831
+ export {};
30832
+ `);
30833
+ await writeFile(join(uiPath, "src/index.ts"), `/**
30834
+ * @${options.scopeName}/ui - Shared UI Components
30835
+ *
30836
+ * This package provides:
30837
+ * - shadcn/ui components (add with: bunx shadcn@latest add <component>)
30838
+ * - Tailwind CSS v4 configuration
30839
+ * - Shared hooks and utilities
30840
+ *
30841
+ * Usage in apps:
30842
+ * - Import globals.css: import '@${options.scopeName}/ui/globals.css';
30843
+ * - Import components: import { Button } from '@${options.scopeName}/ui/components';
30844
+ * - Import utils: import { cn } from '@${options.scopeName}/ui/lib/utils';
30845
+ */
30846
+
30847
+ export * from './lib/utils';
30848
+ export * from './hooks';
30849
+ export * from './components';
30850
+ `);
30851
+ }
30852
+ async function buildTypesPackage(packagesPath, scopeName) {
30853
+ const typesPath = join(packagesPath, "types");
30854
+ await ensureDirectory(join(typesPath, "src"));
30855
+ const { writeTypesPackageJson: writeTypesPackageJson2 } = await Promise.resolve().then(() => (init_package_json(), exports_package_json));
30856
+ await writeTypesPackageJson2(typesPath, scopeName);
30857
+ await writeFile(join(typesPath, "tsconfig.json"), JSON.stringify({
30858
+ extends: "../../tooling/typescript/library.json",
30859
+ compilerOptions: {
30860
+ rootDir: "./src",
30861
+ outDir: "./dist"
30862
+ },
30863
+ include: ["src/**/*"],
30864
+ exclude: ["node_modules"]
30865
+ }, null, 2));
30866
+ await writeFile(join(typesPath, "src/index.ts"), `/**
30867
+ * @${scopeName}/types - Shared TypeScript Types
30868
+ *
30869
+ * Define types that are shared across multiple packages/apps here.
30870
+ */
30871
+
30872
+ // Example types - replace with your actual types
30873
+
30807
30874
  export interface User {
30808
- id: number;
30809
- name: string;
30875
+ id: string;
30810
30876
  email: string;
30877
+ name?: string;
30811
30878
  createdAt: Date;
30879
+ updatedAt: Date;
30812
30880
  }
30813
30881
 
30814
- export interface ApiResponse<T = unknown> {
30882
+ export interface ApiResponse<T> {
30815
30883
  success: boolean;
30816
30884
  data?: T;
30817
- error?: string;
30818
- timestamp: string;
30819
- }
30820
- `;
30821
- await writeFile(join(projectPath, "packages/types/index.ts"), typesIndex);
30822
- const utilsPackageJson = {
30823
- name: `@${context.packageName}/utils`,
30824
- version: "0.0.0",
30825
- private: true,
30826
- main: "./index.ts",
30827
- types: "./index.ts",
30828
- scripts: {},
30829
- dependencies: {},
30830
- devDependencies: {
30831
- typescript: "catalog:"
30832
- }
30885
+ error?: {
30886
+ code: string;
30887
+ message: string;
30833
30888
  };
30834
- await ensureDirectory(join(projectPath, "packages/utils"));
30835
- await writeFile(join(projectPath, "packages/utils/package.json"), JSON.stringify(utilsPackageJson, null, 2));
30836
- const utilsIndex = `// Shared utilities across the monorepo
30837
- export function formatDate(date: Date): string {
30838
- return date.toISOString();
30839
30889
  }
30840
30890
 
30841
- export function capitalize(str: string): string {
30842
- return str.charAt(0).toUpperCase() + str.slice(1);
30891
+ export interface PaginatedResponse<T> extends ApiResponse<T[]> {
30892
+ pagination: {
30893
+ page: number;
30894
+ perPage: number;
30895
+ total: number;
30896
+ totalPages: number;
30897
+ };
30843
30898
  }
30844
- `;
30845
- await writeFile(join(projectPath, "packages/utils/index.ts"), utilsIndex);
30846
- await generateMonorepoReadme(projectPath, context, "bun");
30847
- const tsconfigContent = {
30848
- extends: "./tooling/typescript/base.json",
30849
- compilerOptions: {},
30850
- include: ["apps/*/src/**/*", "packages/*/**/*"],
30899
+ `);
30900
+ }
30901
+ async function buildUtilsPackage(packagesPath, scopeName) {
30902
+ const utilsPath = join(packagesPath, "utils");
30903
+ await ensureDirectory(join(utilsPath, "src"));
30904
+ const { writeUtilsPackageJson: writeUtilsPackageJson2 } = await Promise.resolve().then(() => (init_package_json(), exports_package_json));
30905
+ await writeUtilsPackageJson2(utilsPath, scopeName);
30906
+ await writeFile(join(utilsPath, "tsconfig.json"), JSON.stringify({
30907
+ extends: "../../tooling/typescript/library.json",
30908
+ compilerOptions: {
30909
+ rootDir: "./src",
30910
+ outDir: "./dist"
30911
+ },
30912
+ include: ["src/**/*"],
30851
30913
  exclude: ["node_modules"]
30914
+ }, null, 2));
30915
+ await writeFile(join(utilsPath, "src/index.ts"), `/**
30916
+ * @${scopeName}/utils - Shared Utilities
30917
+ *
30918
+ * Utility functions shared across packages and apps.
30919
+ */
30920
+
30921
+ /**
30922
+ * Format a date to a human-readable string
30923
+ */
30924
+ export function formatDate(date: Date | string, locale = 'en-US'): string {
30925
+ const d = typeof date === 'string' ? new Date(date) : date;
30926
+ return d.toLocaleDateString(locale, {
30927
+ year: 'numeric',
30928
+ month: 'long',
30929
+ day: 'numeric',
30930
+ });
30931
+ }
30932
+
30933
+ /**
30934
+ * Format a number as currency
30935
+ */
30936
+ export function formatCurrency(
30937
+ amount: number,
30938
+ currency = 'USD',
30939
+ locale = 'en-US'
30940
+ ): string {
30941
+ return new Intl.NumberFormat(locale, {
30942
+ style: 'currency',
30943
+ currency,
30944
+ }).format(amount);
30945
+ }
30946
+
30947
+ /**
30948
+ * Sleep for a specified number of milliseconds
30949
+ */
30950
+ export function sleep(ms: number): Promise<void> {
30951
+ return new Promise((resolve) => setTimeout(resolve, ms));
30952
+ }
30953
+
30954
+ /**
30955
+ * Safely parse JSON with a fallback value
30956
+ */
30957
+ export function safeJsonParse<T>(json: string, fallback: T): T {
30958
+ try {
30959
+ return JSON.parse(json) as T;
30960
+ } catch {
30961
+ return fallback;
30962
+ }
30963
+ }
30964
+
30965
+ /**
30966
+ * Generate a random ID
30967
+ */
30968
+ export function generateId(length = 12): string {
30969
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
30970
+ let result = '';
30971
+ for (let i = 0; i < length; i++) {
30972
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
30973
+ }
30974
+ return result;
30975
+ }
30976
+
30977
+ /**
30978
+ * Debounce a function
30979
+ */
30980
+ export function debounce<T extends (...args: unknown[]) => unknown>(
30981
+ func: T,
30982
+ wait: number
30983
+ ): (...args: Parameters<T>) => void {
30984
+ let timeout: ReturnType<typeof setTimeout> | null = null;
30985
+ return (...args: Parameters<T>) => {
30986
+ if (timeout) clearTimeout(timeout);
30987
+ timeout = setTimeout(() => func(...args), wait);
30852
30988
  };
30853
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
30854
30989
  }
30855
- // ../templates/src/builders/web.ts
30856
- init_src();
30857
- init_dist();
30858
- async function buildWebPreset(projectPath, context) {
30859
- await ensureDirectory(join(projectPath, "src/app"));
30860
- await ensureDirectory(join(projectPath, "public"));
30861
- const layoutContent = `import type { Metadata } from 'next'
30862
- import './globals.css'
30990
+
30991
+ /**
30992
+ * Check if running on server
30993
+ * Uses a type-safe approach that works without DOM types
30994
+ */
30995
+ export function isServer(): boolean {
30996
+ return !(typeof window !== 'undefined' && window.document);
30997
+ }
30998
+
30999
+ /**
31000
+ * Check if running on client
31001
+ * Uses a type-safe approach that works without DOM types
31002
+ */
31003
+ export function isClient(): boolean {
31004
+ return typeof window !== 'undefined' && !!window.document;
31005
+ }
31006
+
31007
+ // Declare window for environments without DOM types
31008
+ declare const window: { document?: unknown } | undefined;
31009
+ `);
31010
+ }
31011
+
31012
+ // ../templates/src/builders/full-v2.ts
31013
+ async function buildFullPresetV2(projectPath, context) {
31014
+ const preset = PresetRegistry.get("nextjs-monorepo");
31015
+ if (!preset) {
31016
+ throw new Error("nextjs-monorepo preset not found in registry");
31017
+ }
31018
+ const scopeName = context.packageName;
31019
+ const directories = [
31020
+ "apps/web/src/app",
31021
+ "apps/web/src/components",
31022
+ "apps/web/src/lib",
31023
+ "apps/platform/src/app",
31024
+ "apps/platform/src/components",
31025
+ "apps/platform/src/lib",
31026
+ "apps/api/src/routes",
31027
+ "apps/api/src/middleware",
31028
+ "packages/ui",
31029
+ "packages/types",
31030
+ "packages/utils",
31031
+ "tooling/typescript"
31032
+ ];
31033
+ for (const dir of directories) {
31034
+ await ensureDirectory(join(projectPath, dir));
31035
+ }
31036
+ const catalog = preset.catalogEntries || {};
31037
+ await writeMonorepoRootPackageJson(projectPath, scopeName, catalog, {
31038
+ hasWeb: true,
31039
+ hasPlatform: true,
31040
+ hasApi: true,
31041
+ codeQuality: context.codeQuality === "biome" ? "biome" : "ultracite"
31042
+ });
31043
+ await writeFile(join(projectPath, "bunfig.toml"), generateBunfigContent(context));
31044
+ await writeNextjsAppPackageJson(join(projectPath, "apps/web"), scopeName, "web", {
31045
+ usesUi: true,
31046
+ usesTypes: true,
31047
+ shadcnIconLibrary: context.shadcnIconLibrary || "iconoir"
31048
+ });
31049
+ await writeFile(join(projectPath, "apps/web/src/app/layout.tsx"), `import type { Metadata } from 'next';
31050
+ import '@${scopeName}/ui/globals.css';
30863
31051
 
30864
31052
  export const metadata: Metadata = {
30865
31053
  title: '${context.projectName}',
30866
- description: 'Built with bunkit \uD83C\uDF5E',
30867
- }
31054
+ description: 'Enterprise SaaS built with bunkit',
31055
+ };
30868
31056
 
30869
31057
  export default function RootLayout({
30870
31058
  children,
30871
31059
  }: {
30872
- children: React.ReactNode
31060
+ children: React.ReactNode;
30873
31061
  }) {
30874
31062
  return (
30875
31063
  <html lang="en">
30876
- <body>{children}</body>
31064
+ <body className="antialiased">{children}</body>
30877
31065
  </html>
30878
- )
31066
+ );
30879
31067
  }
30880
- `;
30881
- await writeFile(join(projectPath, "src/app/layout.tsx"), layoutContent);
30882
- const pageContent = `export default function Home() {
31068
+ `);
31069
+ await writeFile(join(projectPath, "apps/web/src/app/page.tsx"), `import { Home, Rocket, Book } from 'iconoir-react';
31070
+
31071
+ export default function HomePage() {
30883
31072
  return (
30884
- <main className="min-h-screen flex items-center justify-center">
30885
- <div className="text-center">
30886
- <h1 className="text-4xl font-bold mb-4">
30887
- Welcome to ${context.projectName} \uD83C\uDF5E
31073
+ <main className="min-h-screen flex items-center justify-center bg-gradient-to-br from-background to-muted">
31074
+ <div className="text-center space-y-8 p-8 max-w-3xl">
31075
+ <div className="flex justify-center">
31076
+ <Home className="w-16 h-16 text-primary" />
31077
+ </div>
31078
+ <h1 className="text-5xl font-bold tracking-tight">
31079
+ Welcome to ${context.projectName}
30888
31080
  </h1>
30889
- <p className="text-gray-600">
30890
- Built with Next.js 16, React 19, and bunkit
31081
+ <p className="text-xl text-muted-foreground">
31082
+ Enterprise-grade SaaS built with Next.js 16, React 19, Hono, and Bun
30891
31083
  </p>
31084
+ <div className="flex gap-4 justify-center pt-4">
31085
+ <a
31086
+ href="/dashboard"
31087
+ className="inline-flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition font-medium"
31088
+ >
31089
+ <Rocket className="w-5 h-5" />
31090
+ Get Started
31091
+ </a>
31092
+ <a
31093
+ href="/docs"
31094
+ className="inline-flex items-center gap-2 px-6 py-3 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/90 transition font-medium"
31095
+ >
31096
+ <Book className="w-5 h-5" />
31097
+ Documentation
31098
+ </a>
31099
+ </div>
30892
31100
  </div>
30893
31101
  </main>
30894
- )
31102
+ );
30895
31103
  }
30896
-
30897
- // Next.js 16 Note:
30898
- // When you add dynamic routes with params, make your component async and await params:
30899
- // export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
30900
- // const { slug } = await params;
30901
- // return <div>{slug}</div>;
30902
- // }
30903
- `;
30904
- await writeFile(join(projectPath, "src/app/page.tsx"), pageContent);
30905
- const globalsCssContent = `@import "tailwindcss";
30906
- `;
30907
- await writeFile(join(projectPath, "src/app/globals.css"), globalsCssContent);
30908
- const nextConfigContent = `import type { NextConfig } from 'next';
31104
+ `);
31105
+ await writeFile(join(projectPath, "apps/web/next.config.ts"), `import type { NextConfig } from 'next';
30909
31106
 
30910
31107
  const nextConfig: NextConfig = {
30911
- /* config options here */
31108
+ transpilePackages: ['@${scopeName}/ui'],
30912
31109
  };
30913
31110
 
30914
31111
  export default nextConfig;
30915
- `;
30916
- await writeFile(join(projectPath, "next.config.ts"), nextConfigContent);
30917
- const tailwindConfigContent = `import type { Config } from 'tailwindcss';
30918
-
30919
- const config: Config = {
30920
- content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
30921
- theme: {
30922
- extend: {},
30923
- },
30924
- plugins: [],
30925
- };
30926
-
30927
- export default config;
30928
- `;
30929
- await writeFile(join(projectPath, "tailwind.config.ts"), tailwindConfigContent);
30930
- const getTsCompilerOptions = () => {
30931
- const baseOptions = {
30932
- target: "ES2017",
30933
- lib: ["dom", "dom.iterable", "esnext"],
30934
- allowJs: true,
30935
- skipLibCheck: true,
30936
- noEmit: true,
30937
- esModuleInterop: true,
30938
- module: "esnext",
30939
- moduleResolution: "bundler",
30940
- resolveJsonModule: true,
30941
- isolatedModules: true,
30942
- jsx: "react-jsx",
30943
- incremental: true,
30944
- plugins: [{ name: "next" }],
30945
- paths: context.pathAliases ? { "@/*": ["./src/*"] } : undefined
30946
- };
30947
- if (context.tsStrictness === "strict") {
30948
- return {
30949
- ...baseOptions,
30950
- strict: true,
30951
- noUnusedLocals: true,
30952
- noUnusedParameters: true,
30953
- noFallthroughCasesInSwitch: true,
30954
- noImplicitReturns: true
30955
- };
30956
- }
30957
- if (context.tsStrictness === "moderate") {
30958
- return {
30959
- ...baseOptions,
30960
- strict: true,
30961
- noUnusedLocals: false,
30962
- noUnusedParameters: false
30963
- };
30964
- }
30965
- return {
30966
- ...baseOptions,
30967
- strict: false,
30968
- noImplicitAny: false
30969
- };
30970
- };
30971
- const tsconfigContent = {
30972
- compilerOptions: getTsCompilerOptions(),
30973
- include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/dev/types/**/*.ts"],
30974
- exclude: ["node_modules"]
30975
- };
30976
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
30977
- const bunfigContent = generateBunfigContent(context);
30978
- await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
30979
- if (context.codeQuality === "ultracite") {
30980
- await setupUltracite(projectPath, context);
30981
- } else {
30982
- await setupBiome(projectPath, context);
30983
- }
30984
- if (context.docker) {
30985
- await setupDocker(projectPath, context);
30986
- }
30987
- if (context.cicd) {
30988
- await setupGitHubActions(projectPath, context);
30989
- }
30990
- if (context.uiLibrary === "shadcn" && context.cssFramework === "tailwind") {
30991
- await setupShadcnWeb(projectPath, context);
30992
- }
30993
- await setupVSCodeDebug(projectPath, context, "web");
30994
- const packageJsonPath = join(projectPath, "package.json");
30995
- const existingPackageJson = JSON.parse(await Bun.file(packageJsonPath).text());
30996
- existingPackageJson.dependencies = {
30997
- react: "^19.2.3",
30998
- "react-dom": "^19.2.3",
30999
- next: "^16.0.10",
31000
- ...existingPackageJson.dependencies
31001
- };
31002
- existingPackageJson.devDependencies = {
31003
- ...existingPackageJson.devDependencies,
31004
- "@types/react": "^19.2.7",
31005
- "@types/react-dom": "^19.2.3",
31006
- "@types/node": "^25.0.3"
31007
- };
31008
- if (!existingPackageJson.scripts) {
31009
- existingPackageJson.scripts = {};
31010
- }
31011
- if (!existingPackageJson.scripts.dev) {
31012
- existingPackageJson.scripts.dev = "bun run --bun next dev --turbopack";
31013
- }
31014
- if (!existingPackageJson.scripts.build) {
31015
- existingPackageJson.scripts.build = "bun run --bun next build";
31016
- }
31017
- if (!existingPackageJson.scripts.start) {
31018
- existingPackageJson.scripts.start = "bun run --bun next start";
31019
- }
31020
- if (!existingPackageJson.scripts.debug) {
31021
- existingPackageJson.scripts.debug = "bun --inspect node_modules/.bin/next dev --turbopack";
31022
- }
31023
- if (!existingPackageJson.scripts["debug:brk"]) {
31024
- existingPackageJson.scripts["debug:brk"] = "bun --inspect-brk node_modules/.bin/next dev --turbopack";
31025
- }
31026
- if (!existingPackageJson.scripts["debug:wait"]) {
31027
- existingPackageJson.scripts["debug:wait"] = "bun --inspect-wait node_modules/.bin/next dev --turbopack";
31028
- }
31029
- await writeFile(packageJsonPath, JSON.stringify(existingPackageJson, null, 2));
31030
- await generateNextjsReadme(projectPath, context);
31031
- }
31032
- // ../templates/src/builders/full-v2.ts
31033
- init_src();
31034
- init_dist();
31035
- init_package_json();
31036
-
31037
- // ../templates/src/shared/ui-package.ts
31038
- init_src();
31039
- init_dist();
31040
- init_package_json();
31041
- function isModernStyle(style) {
31042
- return style === "radix-maia" || style === "radix-vega" || style === "radix-nova" || style === "radix-lyra" || style === "radix-mira";
31043
- }
31044
- async function buildUiPackage(packagesPath, options) {
31045
- const uiPath = join(packagesPath, "ui");
31046
- await ensureDirectory(join(uiPath, "src/components"));
31047
- await ensureDirectory(join(uiPath, "src/hooks"));
31048
- await ensureDirectory(join(uiPath, "src/lib"));
31049
- await ensureDirectory(join(uiPath, "src/styles"));
31050
- await writeUiPackageJson(uiPath, options.scopeName);
31051
- await writeFile(join(uiPath, "tsconfig.json"), JSON.stringify({
31052
- extends: "../../tooling/typescript/library.json",
31112
+ `);
31113
+ await writeFile(join(projectPath, "apps/web/tsconfig.json"), JSON.stringify({
31114
+ extends: "../../tooling/typescript/nextjs.json",
31053
31115
  compilerOptions: {
31054
- jsx: "react-jsx",
31055
- rootDir: "./src",
31056
- outDir: "./dist",
31057
- baseUrl: ".",
31058
31116
  paths: {
31059
31117
  "@/*": ["./src/*"]
31060
31118
  }
31061
31119
  },
31062
- include: ["src/**/*"],
31120
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
31063
31121
  exclude: ["node_modules"]
31064
31122
  }, null, 2));
31065
- const style = options.shadcnStyle || "radix-maia";
31066
- const useModernStyle = isModernStyle(style);
31067
- const iconLibrary = options.shadcnIconLibrary || (useModernStyle ? "phosphor" : "iconoir");
31068
- const componentsJson = {
31069
- $schema: "https://ui.shadcn.com/schema.json",
31070
- style,
31071
- rsc: true,
31072
- tsx: true,
31073
- tailwind: {
31074
- config: "",
31075
- css: "./src/styles/globals.css",
31076
- baseColor: options.shadcnBaseColor || "zinc",
31077
- cssVariables: true,
31078
- prefix: ""
31079
- },
31080
- iconLibrary,
31081
- aliases: {
31082
- components: "@/components",
31083
- utils: "@/lib/utils",
31084
- ui: "@/components/ui",
31085
- lib: "@/lib",
31086
- hooks: "@/hooks"
31087
- }
31088
- };
31089
- if (useModernStyle) {
31090
- componentsJson.menuColor = options.shadcnMenuColor || "default";
31091
- componentsJson.menuAccent = options.shadcnMenuAccent || "subtle";
31092
- componentsJson.registries = {};
31093
- }
31094
- await writeFile(join(uiPath, "components.json"), JSON.stringify(componentsJson, null, 2));
31095
- await writeFile(join(uiPath, "postcss.config.mjs"), `export default {
31123
+ await writeFile(join(projectPath, "apps/web/postcss.config.mjs"), `export default {
31096
31124
  plugins: {
31097
31125
  '@tailwindcss/postcss': {},
31098
31126
  },
31099
31127
  };
31100
31128
  `);
31101
- const appsToScan = options.appsToScan || ["web", "platform"];
31102
- const appSourcePaths = appsToScan.map((app) => `@source "../../../apps/${app}/src/**/*.{ts,tsx}";`);
31103
- const baseColor = options.shadcnBaseColor || "zinc";
31104
- const customRadius = options.shadcnRadius || "0.625rem";
31105
- let globalsCss;
31106
- if (useModernStyle) {
31107
- const modernThemeCSS = generateModernThemeCSS(baseColor, customRadius);
31108
- globalsCss = `@import "tailwindcss";
31109
- @import "tw-animate-css";
31110
- @import "shadcn/tailwind.css";
31111
- ${appSourcePaths.join(`
31112
- `)}
31113
- @source "../**/*.{ts,tsx}";
31129
+ await writeNextjsAppPackageJson(join(projectPath, "apps/platform"), scopeName, "platform", {
31130
+ usesUi: true,
31131
+ usesTypes: true,
31132
+ shadcnIconLibrary: context.shadcnIconLibrary || "iconoir"
31133
+ });
31134
+ await writeFile(join(projectPath, "apps/platform/src/app/layout.tsx"), `import type { Metadata } from 'next';
31135
+ import '@${scopeName}/ui/globals.css';
31114
31136
 
31115
- @custom-variant dark (&:is(.dark *));
31137
+ export const metadata: Metadata = {
31138
+ title: '${context.projectName} - Admin',
31139
+ description: 'Admin dashboard for ${context.projectName}',
31140
+ };
31116
31141
 
31117
- ${modernThemeCSS}
31118
- `;
31119
- } else {
31120
- const theme = themes[baseColor] || themes.zinc;
31121
- const themeCSS = generateThemeCSS(theme, customRadius);
31122
- globalsCss = `@import "tailwindcss";
31123
- ${appSourcePaths.join(`
31124
- `)}
31125
- @source "../**/*.{ts,tsx}";
31142
+ export default function RootLayout({
31143
+ children,
31144
+ }: {
31145
+ children: React.ReactNode;
31146
+ }) {
31147
+ return (
31148
+ <html lang="en">
31149
+ <body className="antialiased">{children}</body>
31150
+ </html>
31151
+ );
31152
+ }
31153
+ `);
31154
+ await writeFile(join(projectPath, "apps/platform/src/app/page.tsx"), `import { ViewGrid, User, Dollar, GraphUp } from 'iconoir-react';
31126
31155
 
31127
- ${themeCSS}
31128
- `;
31129
- }
31130
- await writeFile(join(uiPath, "src/styles/globals.css"), globalsCss);
31131
- await writeFile(join(uiPath, "src/lib/utils.ts"), `import { clsx, type ClassValue } from 'clsx';
31132
- import { twMerge } from 'tailwind-merge';
31156
+ export default function DashboardPage() {
31157
+ return (
31158
+ <main className="min-h-screen bg-background">
31159
+ <div className="max-w-7xl mx-auto py-12 px-4">
31160
+ <header className="mb-8 flex items-center gap-3">
31161
+ <ViewGrid className="w-8 h-8 text-primary" />
31162
+ <div>
31163
+ <h1 className="text-3xl font-bold">Admin Dashboard</h1>
31164
+ <p className="text-muted-foreground">
31165
+ Manage your ${context.projectName} platform
31166
+ </p>
31167
+ </div>
31168
+ </header>
31133
31169
 
31134
- export function cn(...inputs: ClassValue[]) {
31135
- return twMerge(clsx(inputs));
31170
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
31171
+ <div className="bg-card p-6 rounded-lg border shadow-sm">
31172
+ <div className="flex items-center gap-3 mb-2">
31173
+ <User className="w-5 h-5 text-blue-500" />
31174
+ <h2 className="text-lg font-semibold">Users</h2>
31175
+ </div>
31176
+ <p className="text-3xl font-bold">1,234</p>
31177
+ </div>
31178
+ <div className="bg-card p-6 rounded-lg border shadow-sm">
31179
+ <div className="flex items-center gap-3 mb-2">
31180
+ <Dollar className="w-5 h-5 text-green-500" />
31181
+ <h2 className="text-lg font-semibold">Revenue</h2>
31182
+ </div>
31183
+ <p className="text-3xl font-bold">$12,345</p>
31184
+ </div>
31185
+ <div className="bg-card p-6 rounded-lg border shadow-sm">
31186
+ <div className="flex items-center gap-3 mb-2">
31187
+ <GraphUp className="w-5 h-5 text-purple-500" />
31188
+ <h2 className="text-lg font-semibold">Active</h2>
31189
+ </div>
31190
+ <p className="text-3xl font-bold">567</p>
31191
+ </div>
31192
+ </div>
31193
+ </div>
31194
+ </main>
31195
+ );
31136
31196
  }
31137
31197
  `);
31138
- await writeFile(join(uiPath, "src/components/index.ts"), `/**
31139
- * UI Components
31140
- *
31141
- * Add shadcn/ui components using:
31142
- * bunx shadcn@latest add button card dialog ...
31143
- *
31144
- * Then export them here for use across apps.
31145
- */
31198
+ await writeFile(join(projectPath, "apps/platform/next.config.ts"), `import type { NextConfig } from 'next';
31146
31199
 
31147
- // Export components as they are added
31148
- // Example: export { Button } from './ui/button';
31149
-
31150
- // Empty export to make this a valid ES module
31151
- export {};
31152
- `);
31153
- await writeFile(join(uiPath, "src/hooks/index.ts"), `/**
31154
- * Shared Hooks
31155
- *
31156
- * Custom React hooks shared across apps.
31157
- */
31158
-
31159
- // Export hooks as they are added
31160
- // Example: export { useMediaQuery } from './use-media-query';
31161
-
31162
- // Empty export to make this a valid ES module
31163
- export {};
31164
- `);
31165
- await writeFile(join(uiPath, "src/index.ts"), `/**
31166
- * @${options.scopeName}/ui - Shared UI Components
31167
- *
31168
- * This package provides:
31169
- * - shadcn/ui components (add with: bunx shadcn@latest add <component>)
31170
- * - Tailwind CSS v4 configuration
31171
- * - Shared hooks and utilities
31172
- *
31173
- * Usage in apps:
31174
- * - Import globals.css: import '@${options.scopeName}/ui/globals.css';
31175
- * - Import components: import { Button } from '@${options.scopeName}/ui/components';
31176
- * - Import utils: import { cn } from '@${options.scopeName}/ui/lib/utils';
31177
- */
31200
+ const nextConfig: NextConfig = {
31201
+ transpilePackages: ['@${scopeName}/ui'],
31202
+ };
31178
31203
 
31179
- export * from './lib/utils';
31180
- export * from './hooks';
31181
- export * from './components';
31204
+ export default nextConfig;
31182
31205
  `);
31183
- }
31184
- async function buildTypesPackage(packagesPath, scopeName) {
31185
- const typesPath = join(packagesPath, "types");
31186
- await ensureDirectory(join(typesPath, "src"));
31187
- const { writeTypesPackageJson: writeTypesPackageJson2 } = await Promise.resolve().then(() => (init_package_json(), exports_package_json));
31188
- await writeTypesPackageJson2(typesPath, scopeName);
31189
- await writeFile(join(typesPath, "tsconfig.json"), JSON.stringify({
31190
- extends: "../../tooling/typescript/library.json",
31206
+ await writeFile(join(projectPath, "apps/platform/tsconfig.json"), JSON.stringify({
31207
+ extends: "../../tooling/typescript/nextjs.json",
31191
31208
  compilerOptions: {
31192
- rootDir: "./src",
31193
- outDir: "./dist"
31209
+ paths: {
31210
+ "@/*": ["./src/*"]
31211
+ }
31194
31212
  },
31195
- include: ["src/**/*"],
31213
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
31196
31214
  exclude: ["node_modules"]
31197
31215
  }, null, 2));
31198
- await writeFile(join(typesPath, "src/index.ts"), `/**
31199
- * @${scopeName}/types - Shared TypeScript Types
31200
- *
31201
- * Define types that are shared across multiple packages/apps here.
31202
- */
31216
+ await writeFile(join(projectPath, "apps/platform/postcss.config.mjs"), `export default {
31217
+ plugins: {
31218
+ '@tailwindcss/postcss': {},
31219
+ },
31220
+ };
31221
+ `);
31222
+ await writeHonoApiPackageJson(join(projectPath, "apps/api"), scopeName, {
31223
+ usesTypes: true
31224
+ });
31225
+ await writeFile(join(projectPath, "apps/api/src/index.ts"), `import { Hono } from 'hono';
31226
+ import { logger } from 'hono/logger';
31227
+ import { cors } from 'hono/cors';
31203
31228
 
31204
- // Example types - replace with your actual types
31229
+ const app = new Hono();
31205
31230
 
31206
- export interface User {
31207
- id: string;
31208
- email: string;
31209
- name?: string;
31210
- createdAt: Date;
31211
- updatedAt: Date;
31212
- }
31231
+ // Middleware
31232
+ app.use('*', logger());
31233
+ app.use('*', cors());
31213
31234
 
31214
- export interface ApiResponse<T> {
31215
- success: boolean;
31216
- data?: T;
31217
- error?: {
31218
- code: string;
31219
- message: string;
31220
- };
31221
- }
31235
+ // Routes
31236
+ app.get('/', (context) => {
31237
+ return context.json({
31238
+ name: '${context.projectName} API',
31239
+ version: '1.0.0',
31240
+ timestamp: new Date().toISOString(),
31241
+ });
31242
+ });
31222
31243
 
31223
- export interface PaginatedResponse<T> extends ApiResponse<T[]> {
31224
- pagination: {
31225
- page: number;
31226
- perPage: number;
31227
- total: number;
31228
- totalPages: number;
31229
- };
31230
- }
31244
+ app.get('/health', (context) => {
31245
+ return context.json({ status: 'ok' });
31246
+ });
31247
+
31248
+ app.get('/api/users', (context) => {
31249
+ return context.json({
31250
+ users: [
31251
+ { id: '1', email: 'john@example.com', name: 'John Doe' },
31252
+ { id: '2', email: 'jane@example.com', name: 'Jane Smith' },
31253
+ ],
31254
+ });
31255
+ });
31256
+
31257
+ // 404 handler
31258
+ app.notFound((context) => {
31259
+ return context.json({ error: 'Not found' }, 404);
31260
+ });
31261
+
31262
+ // Error handler
31263
+ app.onError((error, context) => {
31264
+ console.error('Error:', error);
31265
+ return context.json({ error: 'Internal server error' }, 500);
31266
+ });
31267
+
31268
+ // Start server with HMR
31269
+ const server = Bun.serve({
31270
+ fetch: app.fetch,
31271
+ port: 3002,
31272
+ development: {
31273
+ hmr: true,
31274
+ },
31275
+ });
31276
+
31277
+ console.log(\`\uD83D\uDE80 ${context.projectName} API running on \${server.url}\`);
31278
+
31279
+ export default app;
31231
31280
  `);
31232
- }
31233
- async function buildUtilsPackage(packagesPath, scopeName) {
31234
- const utilsPath = join(packagesPath, "utils");
31235
- await ensureDirectory(join(utilsPath, "src"));
31236
- const { writeUtilsPackageJson: writeUtilsPackageJson2 } = await Promise.resolve().then(() => (init_package_json(), exports_package_json));
31237
- await writeUtilsPackageJson2(utilsPath, scopeName);
31238
- await writeFile(join(utilsPath, "tsconfig.json"), JSON.stringify({
31239
- extends: "../../tooling/typescript/library.json",
31281
+ await writeFile(join(projectPath, "apps/api/tsconfig.json"), JSON.stringify({
31282
+ extends: "../../tooling/typescript/api.json",
31240
31283
  compilerOptions: {
31241
- rootDir: "./src",
31242
- outDir: "./dist"
31284
+ types: ["bun-types"]
31243
31285
  },
31244
31286
  include: ["src/**/*"],
31245
31287
  exclude: ["node_modules"]
31246
31288
  }, null, 2));
31247
- await writeFile(join(utilsPath, "src/index.ts"), `/**
31248
- * @${scopeName}/utils - Shared Utilities
31249
- *
31250
- * Utility functions shared across packages and apps.
31251
- */
31252
-
31253
- /**
31254
- * Format a date to a human-readable string
31255
- */
31256
- export function formatDate(date: Date | string, locale = 'en-US'): string {
31257
- const d = typeof date === 'string' ? new Date(date) : date;
31258
- return d.toLocaleDateString(locale, {
31259
- year: 'numeric',
31260
- month: 'long',
31261
- day: 'numeric',
31289
+ await buildUiPackage(join(projectPath, "packages"), {
31290
+ scopeName,
31291
+ shadcnStyle: context.shadcnStyle || "radix-maia",
31292
+ shadcnBase: context.shadcnBase || "radix",
31293
+ shadcnBaseColor: context.shadcnBaseColor || "zinc",
31294
+ shadcnIconLibrary: context.shadcnIconLibrary || "iconoir",
31295
+ shadcnMenuAccent: context.shadcnMenuAccent || "subtle",
31296
+ shadcnMenuColor: context.shadcnMenuColor || "default",
31297
+ shadcnRadius: context.shadcnRadius || "0.625rem",
31298
+ shadcnRtl: context.shadcnRtl,
31299
+ appsToScan: ["web", "platform"]
31262
31300
  });
31301
+ await buildTypesPackage(join(projectPath, "packages"), scopeName);
31302
+ await buildUtilsPackage(join(projectPath, "packages"), scopeName);
31303
+ await setupTooling(projectPath, context);
31304
+ await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify({
31305
+ extends: "./tooling/typescript/base.json",
31306
+ include: ["apps/*/src/**/*", "packages/*/src/**/*"],
31307
+ exclude: ["node_modules", "**/node_modules", "**/.next"]
31308
+ }, null, 2));
31309
+ if (context.codeQuality === "ultracite") {
31310
+ await setupUltracite(projectPath, context);
31311
+ } else {
31312
+ await setupBiome(projectPath, context);
31313
+ }
31314
+ if (context.docker) {
31315
+ await setupDocker(projectPath, context);
31316
+ }
31317
+ if (context.cicd) {
31318
+ await setupGitHubActions(projectPath, context);
31319
+ }
31320
+ await setupVSCodeDebug(projectPath, context, "full");
31321
+ await generateMonorepoReadme(projectPath, context, "nextjs");
31263
31322
  }
31323
+ // ../templates/src/builders/minimal.ts
31324
+ init_src();
31325
+ init_dist();
31326
+ async function buildMinimalPreset(projectPath, context) {
31327
+ const indexContent = `console.log('Hello from ${context.projectName}! \uD83C\uDF5E');
31264
31328
 
31265
- /**
31266
- * Format a number as currency
31267
- */
31268
- export function formatCurrency(
31269
- amount: number,
31270
- currency = 'USD',
31271
- locale = 'en-US'
31272
- ): string {
31273
- return new Intl.NumberFormat(locale, {
31274
- style: 'currency',
31275
- currency,
31276
- }).format(amount);
31277
- }
31278
-
31279
- /**
31280
- * Sleep for a specified number of milliseconds
31281
- */
31282
- export function sleep(ms: number): Promise<void> {
31283
- return new Promise((resolve) => setTimeout(resolve, ms));
31284
- }
31329
+ // Your code here
31330
+ const greet = (name: string): void => {
31331
+ console.log(\`Welcome, \${name}!\`);
31332
+ };
31285
31333
 
31286
- /**
31287
- * Safely parse JSON with a fallback value
31288
- */
31289
- export function safeJsonParse<T>(json: string, fallback: T): T {
31290
- try {
31291
- return JSON.parse(json) as T;
31292
- } catch {
31293
- return fallback;
31334
+ greet('Bun');
31335
+ `;
31336
+ await writeFile(join(projectPath, "src/index.ts"), indexContent);
31337
+ const tsconfigContent = {
31338
+ compilerOptions: {
31339
+ lib: ["ESNext"],
31340
+ target: "ESNext",
31341
+ module: "ESNext",
31342
+ moduleDetection: "force",
31343
+ jsx: "react-jsx",
31344
+ allowJs: true,
31345
+ moduleResolution: "bundler",
31346
+ allowImportingTsExtensions: true,
31347
+ verbatimModuleSyntax: true,
31348
+ noEmit: true,
31349
+ strict: true,
31350
+ skipLibCheck: true,
31351
+ noFallthroughCasesInSwitch: true,
31352
+ noUnusedLocals: false,
31353
+ noUnusedParameters: false,
31354
+ noPropertyAccessFromIndexSignature: false,
31355
+ types: ["bun-types"]
31356
+ }
31357
+ };
31358
+ await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
31359
+ const bunfigContent = generateBunfigContent(context);
31360
+ await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
31361
+ if (context.codeQuality === "ultracite") {
31362
+ await setupUltracite(projectPath, context);
31363
+ } else {
31364
+ await setupBiome(projectPath, context);
31294
31365
  }
31366
+ await setupVSCodeDebug(projectPath, context, "minimal");
31367
+ await generateMinimalReadme(projectPath, context);
31295
31368
  }
31296
-
31297
- /**
31298
- * Generate a random ID
31299
- */
31300
- export function generateId(length = 12): string {
31301
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
31302
- let result = '';
31303
- for (let i = 0; i < length; i++) {
31304
- result += chars.charAt(Math.floor(Math.random() * chars.length));
31369
+ // ../templates/src/builders/monorepo-bun.ts
31370
+ init_src();
31371
+ init_dist();
31372
+ var databaseSetupMap6 = {
31373
+ "postgres-drizzle": setupPostgresDrizzle,
31374
+ "postgres-prisma": setupPostgresPrisma,
31375
+ "mysql-drizzle": setupMySQLDrizzle,
31376
+ "mysql-prisma": setupMySQLPrisma,
31377
+ supabase: setupSupabaseOnly,
31378
+ "supabase-drizzle": setupSupabaseDrizzle,
31379
+ "supabase-prisma": setupSupabasePrisma,
31380
+ "sqlite-drizzle": setupSQLiteDrizzle,
31381
+ "sqlite-prisma": setupSQLitePrisma,
31382
+ none: async () => {}
31383
+ };
31384
+ async function buildMonorepoBunPreset(projectPath, context) {
31385
+ await ensureDirectory(join(projectPath, "apps/web"));
31386
+ await ensureDirectory(join(projectPath, "apps/api"));
31387
+ await ensureDirectory(join(projectPath, "packages/types"));
31388
+ await ensureDirectory(join(projectPath, "packages/utils"));
31389
+ if (context.database && context.database !== "none") {
31390
+ await ensureDirectory(join(projectPath, "packages/db"));
31305
31391
  }
31306
- return result;
31307
- }
31308
-
31309
- /**
31310
- * Debounce a function
31311
- */
31312
- export function debounce<T extends (...args: unknown[]) => unknown>(
31313
- func: T,
31314
- wait: number
31315
- ): (...args: Parameters<T>) => void {
31316
- let timeout: ReturnType<typeof setTimeout> | null = null;
31317
- return (...args: Parameters<T>) => {
31318
- if (timeout) clearTimeout(timeout);
31319
- timeout = setTimeout(() => func(...args), wait);
31392
+ const rootPackageJson = {
31393
+ name: `${context.packageName}-monorepo`,
31394
+ version: "0.0.0",
31395
+ private: true,
31396
+ workspaces: ["apps/*", "packages/*"],
31397
+ scripts: {
31398
+ dev: 'bun run --filter "*" dev',
31399
+ build: 'bun run --filter "*" build',
31400
+ lint: context.codeQuality === "biome" ? "biome check ." : "ultracite check .",
31401
+ format: context.codeQuality === "biome" ? "biome check --write ." : "ultracite fix .",
31402
+ test: "bun test",
31403
+ "dev:web": 'bun run --filter "@*/web" dev',
31404
+ "dev:api": 'bun run --filter "@*/api" dev',
31405
+ debug: "bun --inspect apps/api/src/index.ts",
31406
+ "debug:brk": "bun --inspect-brk apps/api/src/index.ts",
31407
+ "debug:wait": "bun --inspect-wait apps/api/src/index.ts"
31408
+ },
31409
+ devDependencies: {
31410
+ "@types/bun": "latest",
31411
+ typescript: "catalog:",
31412
+ ...context.codeQuality === "biome" ? { "@biomejs/biome": "catalog:" } : {}
31413
+ },
31414
+ catalog: {
31415
+ react: "^19.2.3",
31416
+ "react-dom": "^19.2.3",
31417
+ "drizzle-orm": "^0.45.1",
31418
+ "drizzle-kit": "^0.31.8",
31419
+ postgres: "^3.4.7",
31420
+ "@supabase/supabase-js": "^2.88.0",
31421
+ "@prisma/client": "^7.2.0",
31422
+ prisma: "^7.2.0",
31423
+ mysql2: "^3.16.0",
31424
+ "better-auth": "^1.4.7",
31425
+ "next-auth": "^4.24.13",
31426
+ "@auth/drizzle-adapter": "^1.11.1",
31427
+ tailwindcss: "^4.1.18",
31428
+ autoprefixer: "^10.4.23",
31429
+ postcss: "^8.5.6",
31430
+ typescript: "^5.9.3",
31431
+ "@types/react": "^19.2.7",
31432
+ "@types/react-dom": "^19.2.3",
31433
+ "@types/node": "^25.0.3"
31434
+ }
31320
31435
  };
31321
- }
31322
-
31323
- /**
31324
- * Check if running on server
31325
- * Uses a type-safe approach that works without DOM types
31326
- */
31327
- export function isServer(): boolean {
31328
- return !(typeof window !== 'undefined' && window.document);
31329
- }
31330
-
31331
- /**
31332
- * Check if running on client
31333
- * Uses a type-safe approach that works without DOM types
31334
- */
31335
- export function isClient(): boolean {
31336
- return typeof window !== 'undefined' && !!window.document;
31337
- }
31338
-
31339
- // Declare window for environments without DOM types
31340
- declare const window: { document?: unknown } | undefined;
31341
- `);
31342
- }
31343
-
31344
- // ../templates/src/builders/full-v2.ts
31345
- async function buildFullPresetV2(projectPath, context) {
31346
- const preset = PresetRegistry.get("nextjs-monorepo");
31347
- if (!preset) {
31348
- throw new Error("nextjs-monorepo preset not found in registry");
31436
+ await writeFile(join(projectPath, "package.json"), JSON.stringify(rootPackageJson, null, 2));
31437
+ const apiPath = join(projectPath, "apps/api");
31438
+ await setupBunServeNative(apiPath, context, true);
31439
+ const apiPackageJson = {
31440
+ name: `@${context.packageName}/api`,
31441
+ version: "0.0.0",
31442
+ private: true,
31443
+ scripts: {
31444
+ dev: "bun run --hot src/index.ts",
31445
+ start: "bun run src/index.ts",
31446
+ debug: "bun --inspect src/index.ts",
31447
+ "debug:brk": "bun --inspect-brk src/index.ts",
31448
+ "debug:wait": "bun --inspect-wait src/index.ts"
31449
+ },
31450
+ dependencies: {
31451
+ [`@${context.packageName}/types`]: "workspace:*"
31452
+ },
31453
+ devDependencies: {
31454
+ "@types/bun": "latest",
31455
+ typescript: "catalog:"
31456
+ }
31457
+ };
31458
+ await writeFile(join(apiPath, "package.json"), JSON.stringify(apiPackageJson, null, 2));
31459
+ const webPath = join(projectPath, "apps/web");
31460
+ await setupBunFullstack(webPath, context);
31461
+ const webPackageJson = {
31462
+ name: `@${context.packageName}/web`,
31463
+ version: "0.0.0",
31464
+ private: true,
31465
+ scripts: {
31466
+ dev: "bun run --hot src/index.ts",
31467
+ build: "bun build src/index.ts --outdir ./dist --target bun",
31468
+ start: "bun run dist/index.js",
31469
+ debug: "bun --inspect src/index.ts",
31470
+ "debug:brk": "bun --inspect-brk src/index.ts",
31471
+ "debug:wait": "bun --inspect-wait src/index.ts"
31472
+ },
31473
+ dependencies: {
31474
+ react: "catalog:",
31475
+ "react-dom": "catalog:",
31476
+ [`@${context.packageName}/types`]: "workspace:*"
31477
+ },
31478
+ devDependencies: {
31479
+ "@types/react": "catalog:",
31480
+ "@types/react-dom": "catalog:",
31481
+ "@types/bun": "latest",
31482
+ typescript: "catalog:"
31483
+ }
31484
+ };
31485
+ await writeFile(join(webPath, "package.json"), JSON.stringify(webPackageJson, null, 2));
31486
+ if (context.database && context.database !== "none") {
31487
+ await databaseSetupMap6[context.database](join(projectPath, "packages/db"), context, true);
31349
31488
  }
31350
- const scopeName = context.packageName;
31351
- const directories = [
31352
- "apps/web/src/app",
31353
- "apps/web/src/components",
31354
- "apps/web/src/lib",
31355
- "apps/platform/src/app",
31356
- "apps/platform/src/components",
31357
- "apps/platform/src/lib",
31358
- "apps/api/src/routes",
31359
- "apps/api/src/middleware",
31360
- "packages/ui",
31361
- "packages/types",
31362
- "packages/utils",
31363
- "tooling/typescript"
31364
- ];
31365
- for (const dir of directories) {
31366
- await ensureDirectory(join(projectPath, dir));
31489
+ if (context.redis) {
31490
+ await setupRedis(join(projectPath, "packages"), context, true);
31367
31491
  }
31368
- const catalog = preset.catalogEntries || {};
31369
- await writeMonorepoRootPackageJson(projectPath, scopeName, catalog, {
31370
- hasWeb: true,
31371
- hasPlatform: true,
31372
- hasApi: true,
31373
- codeQuality: context.codeQuality === "biome" ? "biome" : "ultracite"
31374
- });
31375
- await writeFile(join(projectPath, "bunfig.toml"), generateBunfigContent(context));
31376
- await writeNextjsAppPackageJson(join(projectPath, "apps/web"), scopeName, "web", { usesUi: true, usesTypes: true });
31377
- await writeFile(join(projectPath, "apps/web/src/app/layout.tsx"), `import type { Metadata } from 'next';
31378
- import '@${scopeName}/ui/globals.css';
31379
-
31380
- export const metadata: Metadata = {
31381
- title: '${context.projectName}',
31382
- description: 'Enterprise SaaS built with bunkit',
31383
- };
31384
-
31385
- export default function RootLayout({
31386
- children,
31387
- }: {
31388
- children: React.ReactNode;
31389
- }) {
31390
- return (
31391
- <html lang="en">
31392
- <body className="antialiased">{children}</body>
31393
- </html>
31394
- );
31492
+ if (context.auth === "better-auth") {
31493
+ await setupBetterAuth(join(projectPath, "packages"), context, true);
31494
+ } else if (context.auth === "nextauth") {
31495
+ await setupNextAuth(join(projectPath, "packages"), context, true);
31496
+ }
31497
+ if (context.useBunSecrets) {
31498
+ await setupBunSecrets(join(projectPath, "packages"), context, true);
31499
+ }
31500
+ if (context.codeQuality === "biome") {
31501
+ await setupBiome(projectPath, context);
31502
+ } else {
31503
+ await setupUltracite(projectPath, context);
31504
+ }
31505
+ if (context.docker) {
31506
+ await setupDocker(projectPath, context);
31507
+ }
31508
+ if (context.cicd) {
31509
+ await setupGitHubActions(projectPath, context);
31510
+ }
31511
+ await setupTooling(projectPath, context);
31512
+ await setupVSCodeDebug(projectPath, context, "full");
31513
+ const bunfigContent = generateBunfigContent(context);
31514
+ await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
31515
+ const typesPackageJson = {
31516
+ name: `@${context.packageName}/types`,
31517
+ version: "0.0.0",
31518
+ private: true,
31519
+ main: "./index.ts",
31520
+ types: "./index.ts",
31521
+ scripts: {},
31522
+ devDependencies: {
31523
+ typescript: "catalog:"
31524
+ }
31525
+ };
31526
+ await ensureDirectory(join(projectPath, "packages/types"));
31527
+ await writeFile(join(projectPath, "packages/types/package.json"), JSON.stringify(typesPackageJson, null, 2));
31528
+ const typesIndex = `// Shared types across the monorepo
31529
+ export interface User {
31530
+ id: number;
31531
+ name: string;
31532
+ email: string;
31533
+ createdAt: Date;
31395
31534
  }
31396
- `);
31397
- await writeFile(join(projectPath, "apps/web/src/app/page.tsx"), `import { Home, Rocket, Book } from 'iconoir-react';
31398
31535
 
31399
- export default function HomePage() {
31400
- return (
31401
- <main className="min-h-screen flex items-center justify-center bg-gradient-to-br from-background to-muted">
31402
- <div className="text-center space-y-8 p-8 max-w-3xl">
31403
- <div className="flex justify-center">
31404
- <Home className="w-16 h-16 text-primary" />
31405
- </div>
31406
- <h1 className="text-5xl font-bold tracking-tight">
31407
- Welcome to ${context.projectName}
31408
- </h1>
31409
- <p className="text-xl text-muted-foreground">
31410
- Enterprise-grade SaaS built with Next.js 16, React 19, Hono, and Bun
31411
- </p>
31412
- <div className="flex gap-4 justify-center pt-4">
31413
- <a
31414
- href="/dashboard"
31415
- className="inline-flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition font-medium"
31416
- >
31417
- <Rocket className="w-5 h-5" />
31418
- Get Started
31419
- </a>
31420
- <a
31421
- href="/docs"
31422
- className="inline-flex items-center gap-2 px-6 py-3 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/90 transition font-medium"
31423
- >
31424
- <Book className="w-5 h-5" />
31425
- Documentation
31426
- </a>
31427
- </div>
31428
- </div>
31429
- </main>
31430
- );
31536
+ export interface ApiResponse<T = unknown> {
31537
+ success: boolean;
31538
+ data?: T;
31539
+ error?: string;
31540
+ timestamp: string;
31541
+ }
31542
+ `;
31543
+ await writeFile(join(projectPath, "packages/types/index.ts"), typesIndex);
31544
+ const utilsPackageJson = {
31545
+ name: `@${context.packageName}/utils`,
31546
+ version: "0.0.0",
31547
+ private: true,
31548
+ main: "./index.ts",
31549
+ types: "./index.ts",
31550
+ scripts: {},
31551
+ dependencies: {},
31552
+ devDependencies: {
31553
+ typescript: "catalog:"
31554
+ }
31555
+ };
31556
+ await ensureDirectory(join(projectPath, "packages/utils"));
31557
+ await writeFile(join(projectPath, "packages/utils/package.json"), JSON.stringify(utilsPackageJson, null, 2));
31558
+ const utilsIndex = `// Shared utilities across the monorepo
31559
+ export function formatDate(date: Date): string {
31560
+ return date.toISOString();
31431
31561
  }
31432
- `);
31433
- await writeFile(join(projectPath, "apps/web/next.config.ts"), `import type { NextConfig } from 'next';
31434
-
31435
- const nextConfig: NextConfig = {
31436
- transpilePackages: ['@${scopeName}/ui'],
31437
- };
31438
31562
 
31439
- export default nextConfig;
31440
- `);
31441
- await writeFile(join(projectPath, "apps/web/tsconfig.json"), JSON.stringify({
31442
- extends: "../../tooling/typescript/nextjs.json",
31443
- compilerOptions: {
31444
- paths: {
31445
- "@/*": ["./src/*"]
31446
- }
31447
- },
31448
- include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
31563
+ export function capitalize(str: string): string {
31564
+ return str.charAt(0).toUpperCase() + str.slice(1);
31565
+ }
31566
+ `;
31567
+ await writeFile(join(projectPath, "packages/utils/index.ts"), utilsIndex);
31568
+ await generateMonorepoReadme(projectPath, context, "bun");
31569
+ const tsconfigContent = {
31570
+ extends: "./tooling/typescript/base.json",
31571
+ compilerOptions: {},
31572
+ include: ["apps/*/src/**/*", "packages/*/**/*"],
31449
31573
  exclude: ["node_modules"]
31450
- }, null, 2));
31451
- await writeFile(join(projectPath, "apps/web/postcss.config.mjs"), `export default {
31452
- plugins: {
31453
- '@tailwindcss/postcss': {},
31454
- },
31455
- };
31456
- `);
31457
- await writeNextjsAppPackageJson(join(projectPath, "apps/platform"), scopeName, "platform", { usesUi: true, usesTypes: true });
31458
- await writeFile(join(projectPath, "apps/platform/src/app/layout.tsx"), `import type { Metadata } from 'next';
31459
- import '@${scopeName}/ui/globals.css';
31574
+ };
31575
+ await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
31576
+ }
31577
+ // ../templates/src/builders/web.ts
31578
+ init_src();
31579
+ init_dist();
31580
+ async function buildWebPreset(projectPath, context) {
31581
+ await ensureDirectory(join(projectPath, "src/app"));
31582
+ await ensureDirectory(join(projectPath, "public"));
31583
+ const layoutContent = `import type { Metadata } from 'next'
31584
+ import './globals.css'
31460
31585
 
31461
31586
  export const metadata: Metadata = {
31462
- title: '${context.projectName} - Admin',
31463
- description: 'Admin dashboard for ${context.projectName}',
31464
- };
31587
+ title: '${context.projectName}',
31588
+ description: 'Built with bunkit \uD83C\uDF5E',
31589
+ }
31465
31590
 
31466
31591
  export default function RootLayout({
31467
31592
  children,
31468
31593
  }: {
31469
- children: React.ReactNode;
31594
+ children: React.ReactNode
31470
31595
  }) {
31471
31596
  return (
31472
31597
  <html lang="en">
31473
- <body className="antialiased">{children}</body>
31598
+ <body>{children}</body>
31474
31599
  </html>
31475
- );
31600
+ )
31476
31601
  }
31477
- `);
31478
- await writeFile(join(projectPath, "apps/platform/src/app/page.tsx"), `import { ViewGrid, User, Dollar, GraphUp } from 'iconoir-react';
31479
-
31480
- export default function DashboardPage() {
31602
+ `;
31603
+ await writeFile(join(projectPath, "src/app/layout.tsx"), layoutContent);
31604
+ const pageContent = `export default function Home() {
31481
31605
  return (
31482
- <main className="min-h-screen bg-background">
31483
- <div className="max-w-7xl mx-auto py-12 px-4">
31484
- <header className="mb-8 flex items-center gap-3">
31485
- <ViewGrid className="w-8 h-8 text-primary" />
31486
- <div>
31487
- <h1 className="text-3xl font-bold">Admin Dashboard</h1>
31488
- <p className="text-muted-foreground">
31489
- Manage your ${context.projectName} platform
31490
- </p>
31491
- </div>
31492
- </header>
31493
-
31494
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
31495
- <div className="bg-card p-6 rounded-lg border shadow-sm">
31496
- <div className="flex items-center gap-3 mb-2">
31497
- <User className="w-5 h-5 text-blue-500" />
31498
- <h2 className="text-lg font-semibold">Users</h2>
31499
- </div>
31500
- <p className="text-3xl font-bold">1,234</p>
31501
- </div>
31502
- <div className="bg-card p-6 rounded-lg border shadow-sm">
31503
- <div className="flex items-center gap-3 mb-2">
31504
- <Dollar className="w-5 h-5 text-green-500" />
31505
- <h2 className="text-lg font-semibold">Revenue</h2>
31506
- </div>
31507
- <p className="text-3xl font-bold">$12,345</p>
31508
- </div>
31509
- <div className="bg-card p-6 rounded-lg border shadow-sm">
31510
- <div className="flex items-center gap-3 mb-2">
31511
- <GraphUp className="w-5 h-5 text-purple-500" />
31512
- <h2 className="text-lg font-semibold">Active</h2>
31513
- </div>
31514
- <p className="text-3xl font-bold">567</p>
31515
- </div>
31516
- </div>
31606
+ <main className="min-h-screen flex items-center justify-center">
31607
+ <div className="text-center">
31608
+ <h1 className="text-4xl font-bold mb-4">
31609
+ Welcome to ${context.projectName} \uD83C\uDF5E
31610
+ </h1>
31611
+ <p className="text-gray-600">
31612
+ Built with Next.js 16, React 19, and bunkit
31613
+ </p>
31517
31614
  </div>
31518
31615
  </main>
31519
- );
31520
- }
31521
- `);
31522
- await writeFile(join(projectPath, "apps/platform/next.config.ts"), `import type { NextConfig } from 'next';
31523
-
31524
- const nextConfig: NextConfig = {
31525
- transpilePackages: ['@${scopeName}/ui'],
31526
- };
31527
-
31528
- export default nextConfig;
31529
- `);
31530
- await writeFile(join(projectPath, "apps/platform/tsconfig.json"), JSON.stringify({
31531
- extends: "../../tooling/typescript/nextjs.json",
31532
- compilerOptions: {
31533
- paths: {
31534
- "@/*": ["./src/*"]
31535
- }
31536
- },
31537
- include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
31538
- exclude: ["node_modules"]
31539
- }, null, 2));
31540
- await writeFile(join(projectPath, "apps/platform/postcss.config.mjs"), `export default {
31541
- plugins: {
31542
- '@tailwindcss/postcss': {},
31543
- },
31544
- };
31545
- `);
31546
- await writeHonoApiPackageJson(join(projectPath, "apps/api"), scopeName, {
31547
- usesTypes: true
31548
- });
31549
- await writeFile(join(projectPath, "apps/api/src/index.ts"), `import { Hono } from 'hono';
31550
- import { logger } from 'hono/logger';
31551
- import { cors } from 'hono/cors';
31552
-
31553
- const app = new Hono();
31554
-
31555
- // Middleware
31556
- app.use('*', logger());
31557
- app.use('*', cors());
31558
-
31559
- // Routes
31560
- app.get('/', (context) => {
31561
- return context.json({
31562
- name: '${context.projectName} API',
31563
- version: '1.0.0',
31564
- timestamp: new Date().toISOString(),
31565
- });
31566
- });
31567
-
31568
- app.get('/health', (context) => {
31569
- return context.json({ status: 'ok' });
31570
- });
31571
-
31572
- app.get('/api/users', (context) => {
31573
- return context.json({
31574
- users: [
31575
- { id: '1', email: 'john@example.com', name: 'John Doe' },
31576
- { id: '2', email: 'jane@example.com', name: 'Jane Smith' },
31577
- ],
31578
- });
31579
- });
31616
+ )
31617
+ }
31580
31618
 
31581
- // 404 handler
31582
- app.notFound((context) => {
31583
- return context.json({ error: 'Not found' }, 404);
31584
- });
31619
+ // Next.js 16 Note:
31620
+ // When you add dynamic routes with params, make your component async and await params:
31621
+ // export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
31622
+ // const { slug } = await params;
31623
+ // return <div>{slug}</div>;
31624
+ // }
31625
+ `;
31626
+ await writeFile(join(projectPath, "src/app/page.tsx"), pageContent);
31627
+ const globalsCssContent = `@import "tailwindcss";
31628
+ `;
31629
+ await writeFile(join(projectPath, "src/app/globals.css"), globalsCssContent);
31630
+ const nextConfigContent = `import type { NextConfig } from 'next';
31585
31631
 
31586
- // Error handler
31587
- app.onError((error, context) => {
31588
- console.error('Error:', error);
31589
- return context.json({ error: 'Internal server error' }, 500);
31590
- });
31632
+ const nextConfig: NextConfig = {
31633
+ /* config options here */
31634
+ };
31591
31635
 
31592
- // Start server with HMR
31593
- const server = Bun.serve({
31594
- fetch: app.fetch,
31595
- port: 3002,
31596
- development: {
31597
- hmr: true,
31598
- },
31599
- });
31636
+ export default nextConfig;
31637
+ `;
31638
+ await writeFile(join(projectPath, "next.config.ts"), nextConfigContent);
31639
+ const tailwindConfigContent = `import type { Config } from 'tailwindcss';
31600
31640
 
31601
- console.log(\`\uD83D\uDE80 ${context.projectName} API running on \${server.url}\`);
31641
+ const config: Config = {
31642
+ content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
31643
+ theme: {
31644
+ extend: {},
31645
+ },
31646
+ plugins: [],
31647
+ };
31602
31648
 
31603
- export default app;
31604
- `);
31605
- await writeFile(join(projectPath, "apps/api/tsconfig.json"), JSON.stringify({
31606
- extends: "../../tooling/typescript/api.json",
31607
- compilerOptions: {
31608
- types: ["bun-types"]
31609
- },
31610
- include: ["src/**/*"],
31649
+ export default config;
31650
+ `;
31651
+ await writeFile(join(projectPath, "tailwind.config.ts"), tailwindConfigContent);
31652
+ const getTsCompilerOptions = () => {
31653
+ const baseOptions = {
31654
+ target: "ES2017",
31655
+ lib: ["dom", "dom.iterable", "esnext"],
31656
+ allowJs: true,
31657
+ skipLibCheck: true,
31658
+ noEmit: true,
31659
+ esModuleInterop: true,
31660
+ module: "esnext",
31661
+ moduleResolution: "bundler",
31662
+ resolveJsonModule: true,
31663
+ isolatedModules: true,
31664
+ jsx: "react-jsx",
31665
+ incremental: true,
31666
+ plugins: [{ name: "next" }],
31667
+ paths: context.pathAliases ? { "@/*": ["./src/*"] } : undefined
31668
+ };
31669
+ if (context.tsStrictness === "strict") {
31670
+ return {
31671
+ ...baseOptions,
31672
+ strict: true,
31673
+ noUnusedLocals: true,
31674
+ noUnusedParameters: true,
31675
+ noFallthroughCasesInSwitch: true,
31676
+ noImplicitReturns: true
31677
+ };
31678
+ }
31679
+ if (context.tsStrictness === "moderate") {
31680
+ return {
31681
+ ...baseOptions,
31682
+ strict: true,
31683
+ noUnusedLocals: false,
31684
+ noUnusedParameters: false
31685
+ };
31686
+ }
31687
+ return {
31688
+ ...baseOptions,
31689
+ strict: false,
31690
+ noImplicitAny: false
31691
+ };
31692
+ };
31693
+ const tsconfigContent = {
31694
+ compilerOptions: getTsCompilerOptions(),
31695
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/dev/types/**/*.ts"],
31611
31696
  exclude: ["node_modules"]
31612
- }, null, 2));
31613
- await buildUiPackage(join(projectPath, "packages"), {
31614
- scopeName,
31615
- shadcnStyle: context.shadcnStyle || "radix-maia",
31616
- shadcnBase: context.shadcnBase || "radix",
31617
- shadcnBaseColor: context.shadcnBaseColor || "zinc",
31618
- shadcnIconLibrary: context.shadcnIconLibrary || "phosphor",
31619
- shadcnMenuAccent: context.shadcnMenuAccent || "subtle",
31620
- shadcnMenuColor: context.shadcnMenuColor || "default",
31621
- shadcnRadius: context.shadcnRadius || "0.625rem",
31622
- appsToScan: ["web", "platform"]
31623
- });
31624
- await buildTypesPackage(join(projectPath, "packages"), scopeName);
31625
- await buildUtilsPackage(join(projectPath, "packages"), scopeName);
31626
- await setupTooling(projectPath, context);
31627
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify({
31628
- extends: "./tooling/typescript/base.json",
31629
- include: ["apps/*/src/**/*", "packages/*/src/**/*"],
31630
- exclude: ["node_modules", "**/node_modules", "**/.next"]
31631
- }, null, 2));
31697
+ };
31698
+ await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
31699
+ const bunfigContent = generateBunfigContent(context);
31700
+ await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
31632
31701
  if (context.codeQuality === "ultracite") {
31633
31702
  await setupUltracite(projectPath, context);
31634
31703
  } else {
@@ -31640,13 +31709,52 @@ export default app;
31640
31709
  if (context.cicd) {
31641
31710
  await setupGitHubActions(projectPath, context);
31642
31711
  }
31643
- await setupVSCodeDebug(projectPath, context, "full");
31644
- await generateMonorepoReadme(projectPath, context, "nextjs");
31712
+ if (context.uiLibrary === "shadcn" && context.cssFramework === "tailwind") {
31713
+ await setupShadcnWeb(projectPath, context);
31714
+ }
31715
+ await setupVSCodeDebug(projectPath, context, "web");
31716
+ const packageJsonPath = join(projectPath, "package.json");
31717
+ const existingPackageJson = JSON.parse(await Bun.file(packageJsonPath).text());
31718
+ existingPackageJson.dependencies = {
31719
+ react: "^19.2.3",
31720
+ "react-dom": "^19.2.3",
31721
+ next: "^16.0.10",
31722
+ ...existingPackageJson.dependencies
31723
+ };
31724
+ existingPackageJson.devDependencies = {
31725
+ ...existingPackageJson.devDependencies,
31726
+ "@types/react": "^19.2.7",
31727
+ "@types/react-dom": "^19.2.3",
31728
+ "@types/node": "^25.0.3"
31729
+ };
31730
+ if (!existingPackageJson.scripts) {
31731
+ existingPackageJson.scripts = {};
31732
+ }
31733
+ if (!existingPackageJson.scripts.dev) {
31734
+ existingPackageJson.scripts.dev = "bun run --bun next dev --turbopack";
31735
+ }
31736
+ if (!existingPackageJson.scripts.build) {
31737
+ existingPackageJson.scripts.build = "bun run --bun next build";
31738
+ }
31739
+ if (!existingPackageJson.scripts.start) {
31740
+ existingPackageJson.scripts.start = "bun run --bun next start";
31741
+ }
31742
+ if (!existingPackageJson.scripts.debug) {
31743
+ existingPackageJson.scripts.debug = "bun --inspect node_modules/.bin/next dev --turbopack";
31744
+ }
31745
+ if (!existingPackageJson.scripts["debug:brk"]) {
31746
+ existingPackageJson.scripts["debug:brk"] = "bun --inspect-brk node_modules/.bin/next dev --turbopack";
31747
+ }
31748
+ if (!existingPackageJson.scripts["debug:wait"]) {
31749
+ existingPackageJson.scripts["debug:wait"] = "bun --inspect-wait node_modules/.bin/next dev --turbopack";
31750
+ }
31751
+ await writeFile(packageJsonPath, JSON.stringify(existingPackageJson, null, 2));
31752
+ await generateNextjsReadme(projectPath, context);
31645
31753
  }
31646
- // ../templates/src/shared/index.ts
31647
- init_package_json();
31648
31754
  // ../templates/src/render.ts
31649
31755
  var import_ejs = __toESM(require_ejs(), 1);
31756
+ // ../templates/src/shared/index.ts
31757
+ init_package_json();
31650
31758
  // src/commands/add/component.ts
31651
31759
  init_dist();
31652
31760
  async function getPackageName(cwd2) {
@@ -33117,29 +33225,54 @@ async function enhancedInitCommand(options = {}) {
33117
33225
  options: [
33118
33226
  {
33119
33227
  value: "radix-maia",
33120
- label: "Maia (Recommended)",
33228
+ label: "Maia \u2014 Radix (Recommended)",
33121
33229
  hint: "Modern, clean design with soft shadows and refined aesthetics"
33122
33230
  },
33123
33231
  {
33124
33232
  value: "radix-vega",
33125
- label: "Vega",
33233
+ label: "Vega \u2014 Radix",
33126
33234
  hint: "Bold, vibrant design with stronger colors and contrast"
33127
33235
  },
33128
33236
  {
33129
33237
  value: "radix-nova",
33130
- label: "Nova",
33238
+ label: "Nova \u2014 Radix",
33131
33239
  hint: "Minimalist design with subtle accents and clean lines"
33132
33240
  },
33133
33241
  {
33134
33242
  value: "radix-lyra",
33135
- label: "Lyra",
33243
+ label: "Lyra \u2014 Radix",
33136
33244
  hint: "Elegant design with refined typography and spacing"
33137
33245
  },
33138
33246
  {
33139
33247
  value: "radix-mira",
33140
- label: "Mira",
33248
+ label: "Mira \u2014 Radix",
33141
33249
  hint: "Playful design with rounded elements and soft colors"
33142
33250
  },
33251
+ {
33252
+ value: "base-maia",
33253
+ label: "Maia \u2014 Base UI",
33254
+ hint: "Clean design using Base UI primitives (alternative to Radix)"
33255
+ },
33256
+ {
33257
+ value: "base-vega",
33258
+ label: "Vega \u2014 Base UI",
33259
+ hint: "Bold design using Base UI primitives"
33260
+ },
33261
+ {
33262
+ value: "base-nova",
33263
+ label: "Nova \u2014 Base UI",
33264
+ hint: "Minimalist design using Base UI primitives"
33265
+ },
33266
+ {
33267
+ value: "base-lyra",
33268
+ label: "Lyra \u2014 Base UI",
33269
+ hint: "Elegant design using Base UI primitives"
33270
+ },
33271
+ {
33272
+ value: "base-mira",
33273
+ label: "Mira \u2014 Base UI",
33274
+ hint: "Playful design using Base UI primitives"
33275
+ },
33143
33276
  {
33144
33277
  value: "new-york",
33145
33278
  label: "New York (Legacy)",
@@ -33227,21 +33360,21 @@ async function enhancedInitCommand(options = {}) {
33227
33360
  }
33228
33361
  }
33229
33362
  let shadcnIconLibrary = getOptionValue("BUNKIT_SHADCN_ICON_LIBRARY", options.shadcnIconLibrary);
33230
- const isModernShadcnStyle = shadcnStyle === "radix-maia" || shadcnStyle === "radix-vega" || shadcnStyle === "radix-nova" || shadcnStyle === "radix-lyra" || shadcnStyle === "radix-mira";
33363
+ const isModernStyle = shadcnStyle?.startsWith("radix-") || shadcnStyle?.startsWith("base-");
33231
33364
  if (!shadcnIconLibrary && uiLibrary === "shadcn") {
33232
33365
  if (!isNonInteractive) {
33233
33366
  shadcnIconLibrary = await ve({
33234
33367
  message: "\uD83D\uDD23 Icon library",
33235
33368
  options: [
33236
33369
  {
33237
- value: "phosphor",
33238
- label: isModernShadcnStyle ? "Phosphor Icons (Recommended)" : "Phosphor Icons",
33239
- hint: "Modern icon library with consistent design - default for new shadcn/ui styles"
33370
+ value: "iconoir",
33371
+ label: "Iconoir (Recommended)",
33372
+ hint: "bunkit default - 1600+ lightweight, tree-shakeable icons"
33240
33373
  },
33241
33374
  {
33242
- value: "iconoir",
33243
- label: !isModernShadcnStyle ? "Iconoir (Recommended)" : "Iconoir",
33244
- hint: "Lightweight icons with great variety - alternative choice"
33375
+ value: "phosphor",
33376
+ label: "Phosphor Icons",
33377
+ hint: "Modern icon library with consistent design - shadcn/ui default for new styles"
33245
33378
  },
33246
33379
  {
33247
33380
  value: "lucide",
@@ -33255,39 +33388,19 @@ async function enhancedInitCommand(options = {}) {
33255
33388
  process.exit(0);
33256
33389
  }
33257
33390
  } else {
33258
- shadcnIconLibrary = isModernShadcnStyle ? "phosphor" : "iconoir";
33391
+ shadcnIconLibrary = "iconoir";
33259
33392
  }
33260
33393
  }
33261
33394
  let shadcnBase = getOptionValue("BUNKIT_SHADCN_BASE", options.shadcnBase);
33262
- if (!shadcnBase && uiLibrary === "shadcn" && isModernShadcnStyle) {
33263
- if (!isNonInteractive) {
33264
- shadcnBase = await ve({
33265
- message: "\uD83E\uDDF1 Component foundation",
33266
- options: [
33267
- {
33268
- value: "radix",
33269
- label: "Radix UI (Recommended)",
33270
- hint: "Battle-tested accessibility primitives - industry standard"
33271
- },
33272
- {
33273
- value: "base-ui",
33274
- label: "Base UI",
33275
- hint: "Modern alternative with similar API - newer, experimental"
33276
- }
33277
- ]
33278
- });
33279
- if (pD(shadcnBase)) {
33280
- xe("Operation cancelled.");
33281
- process.exit(0);
33282
- }
33395
+ if (!shadcnBase && uiLibrary === "shadcn") {
33396
+ if (shadcnStyle?.startsWith("base-")) {
33397
+ shadcnBase = "base-ui";
33283
33398
  } else {
33284
33399
  shadcnBase = "radix";
33285
33400
  }
33286
- } else if (!shadcnBase) {
33287
- shadcnBase = "radix";
33288
33401
  }
33289
33402
  let shadcnMenuAccent = getOptionValue("BUNKIT_SHADCN_MENU_ACCENT", options.shadcnMenuAccent);
33290
- if (!shadcnMenuAccent && uiLibrary === "shadcn" && isModernShadcnStyle) {
33403
+ if (!shadcnMenuAccent && uiLibrary === "shadcn" && isModernStyle) {
33291
33404
  if (!isNonInteractive) {
33292
33405
  shadcnMenuAccent = await ve({
33293
33406
  message: "\u2728 Menu accent style",
@@ -33315,7 +33428,7 @@ async function enhancedInitCommand(options = {}) {
33315
33428
  shadcnMenuAccent = "subtle";
33316
33429
  }
33317
33430
  let shadcnMenuColor = getOptionValue("BUNKIT_SHADCN_MENU_COLOR", options.shadcnMenuColor);
33318
- if (!shadcnMenuColor && uiLibrary === "shadcn" && isModernShadcnStyle) {
33431
+ if (!shadcnMenuColor && uiLibrary === "shadcn" && isModernStyle) {
33319
33432
  if (!isNonInteractive) {
33320
33433
  shadcnMenuColor = await ve({
33321
33434
  message: "\uD83C\uDFAF Menu color scheme",
@@ -33342,6 +33455,21 @@ async function enhancedInitCommand(options = {}) {
33342
33455
  } else if (!shadcnMenuColor) {
33343
33456
  shadcnMenuColor = "default";
33344
33457
  }
33458
+ let shadcnRtl = getOptionValue("BUNKIT_SHADCN_RTL", options.shadcnRtl, false);
33459
+ if (shadcnRtl === undefined && uiLibrary === "shadcn" && isModernStyle) {
33460
+ if (!isNonInteractive) {
33461
+ shadcnRtl = await ye({
33462
+ message: "\uD83D\uDD04 Enable RTL (right-to-left) support",
33463
+ initialValue: false
33464
+ });
33465
+ if (pD(shadcnRtl)) {
33466
+ xe("Operation cancelled.");
33467
+ process.exit(0);
33468
+ }
33469
+ } else {
33470
+ shadcnRtl = false;
33471
+ }
33472
+ }
33345
33473
  let testing = getOptionValue("BUNKIT_TESTING", options.testing, "bun-test");
33346
33474
  if (!testing) {
33347
33475
  if (!isNonInteractive) {
@@ -33588,6 +33716,7 @@ async function enhancedInitCommand(options = {}) {
33588
33716
  shadcnMenuAccent,
33589
33717
  shadcnMenuColor,
33590
33718
  shadcnRadius,
33719
+ shadcnRtl: shadcnRtl || false,
33591
33720
  supabasePreset,
33592
33721
  supabaseFeatures,
33593
33722
  supabaseWithDrizzle
@@ -33797,6 +33926,130 @@ async function enhancedInitCommand(options = {}) {
33797
33926
  }
33798
33927
  }
33799
33928
 
33929
+ // src/commands/migrate.ts
33930
+ import { existsSync as existsSync3 } from "fs";
33931
+ init_source();
33932
+ init_dist();
33933
+ var VALID_MIGRATIONS = ["radix", "rtl", "icons"];
33934
+ var MIGRATION_DESCRIPTIONS = {
33935
+ radix: "Migrate to unified radix-ui package (replaces @radix-ui/react-* packages)",
33936
+ rtl: "Enable RTL (right-to-left) support in shadcn/ui components",
33937
+ icons: "Migrate icon library (e.g., lucide to iconoir or phosphor)"
33938
+ };
33939
+ function detectShadcnRoot(projectPath) {
33940
+ const monorepoPath = join(projectPath, "packages/ui");
33941
+ if (existsSync3(join(monorepoPath, "components.json"))) {
33942
+ return monorepoPath;
33943
+ }
33944
+ if (existsSync3(join(projectPath, "components.json"))) {
33945
+ return projectPath;
33946
+ }
33947
+ return null;
33948
+ }
33949
+ async function runBunInstall(projectPath) {
33950
+ const installSpinner = Y2();
33951
+ installSpinner.start("Resolving dependencies (bun install)...");
33952
+ try {
33953
+ const installProcess = Bun.spawn(["bun", "install"], {
33954
+ cwd: projectPath,
33955
+ stdout: "pipe",
33956
+ stderr: "pipe"
33957
+ });
33958
+ await installProcess.exited;
33959
+ if (installProcess.exitCode !== 0) {
33960
+ const stderr = await new Response(installProcess.stderr).text();
33961
+ installSpinner.stop(source_default.red("Failed to install dependencies"));
33962
+ Me(stderr, "Error Details");
33963
+ return false;
33964
+ }
33965
+ installSpinner.stop(source_default.green("Dependencies resolved"));
33966
+ return true;
33967
+ } catch (error) {
33968
+ installSpinner.stop(source_default.red("Failed to install dependencies"));
33969
+ Me(String(error), "Error Details");
33970
+ return false;
33971
+ }
33972
+ }
33973
+ async function runShadcnMigrate(shadcnRoot, migrationType) {
33974
+ const migrateSpinner = Y2();
33975
+ migrateSpinner.start(`Running shadcn migrate ${migrationType}...`);
33976
+ try {
33977
+ const migrateProcess = Bun.spawn(["bunx", "shadcn@latest", "migrate", migrationType], {
33978
+ cwd: shadcnRoot,
33979
+ stdout: "pipe",
33980
+ stderr: "pipe"
33981
+ });
33982
+ await migrateProcess.exited;
33983
+ if (migrateProcess.exitCode !== 0) {
33984
+ const stderr = await new Response(migrateProcess.stderr).text();
33985
+ migrateSpinner.stop(source_default.red(`Migration failed: ${migrationType}`));
33986
+ Me(stderr, "Error Details");
33987
+ return false;
33988
+ }
33989
+ migrateSpinner.stop(source_default.green(`Migration complete: ${migrationType}`));
33990
+ return true;
33991
+ } catch (error) {
33992
+ migrateSpinner.stop(source_default.red(`Migration failed: ${migrationType}`));
33993
+ Me(String(error), "Error Details");
33994
+ return false;
33995
+ }
33996
+ }
33997
+ function showPostMigrationAdvice(migrationType, isMonorepo) {
33998
+ const advice = [];
33999
+ switch (migrationType) {
34000
+ case "radix":
34001
+ advice.push("The unified radix-ui package replaces individual @radix-ui/react-* packages.", "Old packages can be removed from your package.json.", isMonorepo ? "Check packages/ui/package.json and root catalog for cleanup." : "Check package.json for cleanup.", "", "Run: bun install (to update lockfile)");
34002
+ break;
34003
+ case "rtl":
34004
+ advice.push("RTL support has been added to your components.", "Class names have been transformed for RTL compatibility.", 'components.json now includes "rtl": true.', "", 'Set dir="rtl" on your <html> element to activate RTL layout.');
34005
+ break;
34006
+ case "icons":
34007
+ advice.push("Icon imports have been updated in your components.", "Check components.json to verify the iconLibrary field.", "", "You may need to install the new icon package:", " bun add iconoir-react (bunkit default)", " bun add @phosphor-icons/react", " bun add lucide-react");
34008
+ break;
34009
+ }
34010
+ if (advice.length > 0) {
34011
+ Me(advice.join(`
34012
+ `), "Post-Migration");
34013
+ }
34014
+ }
34015
+ async function migrateCommand(migrationType) {
34016
+ if (!migrationType) {
34017
+ migrationType = await ve({
34018
+ message: "Select migration type",
34019
+ options: VALID_MIGRATIONS.map((migration2) => ({
34020
+ value: migration2,
34021
+ label: migration2,
34022
+ hint: MIGRATION_DESCRIPTIONS[migration2]
34023
+ }))
34024
+ });
34025
+ if (pD(migrationType)) {
34026
+ xe("Operation cancelled.");
34027
+ process.exit(0);
34028
+ }
34029
+ }
34030
+ if (!VALID_MIGRATIONS.includes(migrationType)) {
34031
+ throw new Error(`Invalid migration type: "${migrationType}". ` + `Valid types: ${VALID_MIGRATIONS.join(", ")}`);
34032
+ }
34033
+ const migration = migrationType;
34034
+ const projectPath = process.cwd();
34035
+ const shadcnRoot = detectShadcnRoot(projectPath);
34036
+ if (!shadcnRoot) {
34037
+ throw new Error("Could not find components.json. " + "Run this command from a project with shadcn/ui configured, " + "or from a monorepo root with packages/ui/components.json.");
34038
+ }
34039
+ const isMonorepo = shadcnRoot !== projectPath;
34040
+ M2.info(`${source_default.bold("Migration:")} ${migration} \u2014 ${MIGRATION_DESCRIPTIONS[migration]}`);
34041
+ M2.info(`${source_default.bold("Location:")} ${isMonorepo ? "packages/ui (monorepo)" : "(project root)"}`);
34042
+ const installSuccess = await runBunInstall(projectPath);
34043
+ if (!installSuccess) {
34044
+ throw new Error("Failed to resolve dependencies. Fix the errors above and try again.");
34045
+ }
34046
+ const migrateSuccess = await runShadcnMigrate(shadcnRoot, migration);
34047
+ if (!migrateSuccess) {
34048
+ throw new Error(`Migration "${migration}" failed. Check the errors above.`);
34049
+ }
34050
+ showPostMigrationAdvice(migration, isMonorepo);
34051
+ }
34052
+
33800
34053
  // src/commands/preset.ts
33801
34054
  init_src();
33802
34055
  init_boxen();
@@ -33910,12 +34163,13 @@ Examples:
33910
34163
  $ bunkit create nextjs my-app Create a Next.js web application
33911
34164
  $ bunkit add workspace Add a new workspace to monorepo
33912
34165
  $ bunkit add component --all Browse and add shadcn/ui components
34166
+ $ bunkit migrate radix Migrate to unified radix-ui package
33913
34167
  $ bunkit catalog add zod ^3.24.1 Add package to dependency catalog
33914
34168
  $ bunkit preset list List all custom presets
33915
34169
 
33916
34170
  For more information, visit: https://github.com/Arakiss/bunkit
33917
34171
  `);
33918
- program2.command("init").description("Create a new project with full customization options").alias("i").option("--name <name>", "Project name (kebab-case recommended, e.g., my-awesome-app)").option("--preset <preset>", "Project preset: minimal | nextjs | hono-api | bun-api | bun-fullstack | nextjs-monorepo | bun-monorepo | enterprise-monorepo").option("--database <database>", "Database option: postgres-drizzle | postgres-prisma | mysql-drizzle | mysql-prisma | supabase | supabase-drizzle | supabase-prisma | sqlite-drizzle | sqlite-prisma | none").option("--auth <auth>", "Authentication system: better-auth | nextauth | supabase | none").option("--redis", "Enable Redis cache/session store").option("--use-bun-secrets", "Use Bun.secrets API instead of .env files").option("--supabase-preset <preset>", "Supabase configuration preset: full-stack | auth-only | database-only | custom").option("--supabase-features <features>", "Comma-separated Supabase features: auth,storage,realtime,edge-functions,database").option("--code-quality <tool>", "Code quality tool: ultracite | biome").option("--ts-strictness <level>", "TypeScript strictness level: strict | moderate | loose").option("--ui-library <library>", "UI component library: shadcn | none").option("--css-framework <framework>", "CSS framework: tailwind | vanilla | css-modules").option("--shadcn-style <style>", "shadcn/ui style: radix-maia | radix-vega | radix-nova | radix-lyra | radix-mira | new-york | default").option("--shadcn-base <base>", "shadcn/ui component foundation: radix | base-ui").option("--shadcn-base-color <color>", "shadcn/ui base color theme: zinc | slate | stone | gray | neutral").option("--shadcn-icon-library <library>", "shadcn/ui icon library: phosphor | lucide | iconoir").option("--shadcn-menu-accent <accent>", "shadcn/ui menu accent style: subtle | bold").option("--shadcn-menu-color <color>", "shadcn/ui menu color: default | muted").option("--shadcn-radius <radius>", "shadcn/ui border radius (CSS value, e.g., 0.5rem, 0.625rem)").option("--testing <framework>", "Testing framework: bun-test | vitest | none").option("--docker", "Include Docker configuration files").option("--cicd", "Include GitHub Actions CI/CD workflow").option("--no-git", "Skip Git repository initialization").option("--no-install", "Skip dependency installation after project creation").option("--non-interactive", "Run in non-interactive mode (requires all options via flags)").option("--save-preset <name>", "Save current configuration as a custom preset").option("--load-preset <name>", "Load configuration from a custom preset").addHelpText("after", `
34172
+ program2.command("init").description("Create a new project with full customization options").alias("i").option("--name <name>", "Project name (kebab-case recommended, e.g., my-awesome-app)").option("--preset <preset>", "Project preset: minimal | nextjs | hono-api | bun-api | bun-fullstack | nextjs-monorepo | bun-monorepo | enterprise-monorepo").option("--database <database>", "Database option: postgres-drizzle | postgres-prisma | mysql-drizzle | mysql-prisma | supabase | supabase-drizzle | supabase-prisma | sqlite-drizzle | sqlite-prisma | none").option("--auth <auth>", "Authentication system: better-auth | nextauth | supabase | none").option("--redis", "Enable Redis cache/session store").option("--use-bun-secrets", "Use Bun.secrets API instead of .env files").option("--supabase-preset <preset>", "Supabase configuration preset: full-stack | auth-only | database-only | custom").option("--supabase-features <features>", "Comma-separated Supabase features: auth,storage,realtime,edge-functions,database").option("--code-quality <tool>", "Code quality tool: ultracite | biome").option("--ts-strictness <level>", "TypeScript strictness level: strict | moderate | loose").option("--ui-library <library>", "UI component library: shadcn | none").option("--css-framework <framework>", "CSS framework: tailwind | vanilla | css-modules").option("--shadcn-style <style>", "shadcn/ui style: radix-maia | radix-vega | radix-nova | radix-lyra | radix-mira | base-maia | base-vega | base-nova | base-lyra | base-mira | new-york | default").option("--shadcn-base <base>", "shadcn/ui component foundation: radix | base-ui").option("--shadcn-base-color <color>", "shadcn/ui base color theme: zinc | slate | stone | gray | neutral").option("--shadcn-icon-library <library>", "shadcn/ui icon library: phosphor | lucide | iconoir").option("--shadcn-menu-accent <accent>", "shadcn/ui menu accent style: subtle | bold").option("--shadcn-menu-color <color>", "shadcn/ui menu color: default | muted").option("--shadcn-radius <radius>", "shadcn/ui border radius (CSS value, e.g., 0.5rem, 0.625rem)").option("--shadcn-rtl", "Enable RTL (right-to-left) support for shadcn/ui components").option("--testing <framework>", "Testing framework: bun-test | vitest | none").option("--docker", "Include Docker configuration files").option("--cicd", "Include GitHub Actions CI/CD workflow").option("--no-git", "Skip Git repository initialization").option("--no-install", "Skip dependency installation after project creation").option("--non-interactive", "Run in non-interactive mode (requires all options via flags)").option("--save-preset <name>", "Save current configuration as a custom preset").option("--load-preset <name>", "Load configuration from a custom preset").addHelpText("after", `
33919
34173
  Examples:
33920
34174
  $ bunkit init Interactive project creation
33921
34175
  $ bunkit init --name my-app --preset nextjs Quick web app creation
@@ -34035,6 +34289,28 @@ Features:
34035
34289
  process.exit(1);
34036
34290
  }
34037
34291
  });
34292
+ program2.command("migrate").argument("[type]", "Migration type: radix | rtl | icons").description("Run shadcn/ui migrations (unified radix-ui, RTL, icon library)").addHelpText("after", `
34293
+ Examples:
34294
+ $ bunkit migrate Interactive migration selection
34295
+ $ bunkit migrate radix Migrate to unified radix-ui package
34296
+ $ bunkit migrate rtl Enable RTL support in components
34297
+ $ bunkit migrate icons Migrate icon library
34298
+
34299
+ Migrations:
34300
+ radix Replace @radix-ui/react-* packages with unified radix-ui
34301
+ rtl Add RTL class transforms to installed components
34302
+ icons Migrate between icon libraries (lucide, iconoir, phosphor)
34303
+ `).action(async (migrationType) => {
34304
+ showBanner(VERSION);
34305
+ try {
34306
+ await migrateCommand(migrationType);
34307
+ Se(import_picocolors7.default.green("Migration completed successfully!"));
34308
+ } catch (error) {
34309
+ M2.error(error.message);
34310
+ Se(import_picocolors7.default.red("Migration failed"));
34311
+ process.exit(1);
34312
+ }
34313
+ });
34038
34314
  var catalogCmd = new Command("catalog").alias("cat").description("Manage dependency catalog for version synchronization").addHelpText("after", `
34039
34315
  Subcommands:
34040
34316
  add <package> [version] Add package to catalog