bunkit-cli 1.3.2 → 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 +1331 -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,11 +21982,13 @@ async function writeUiPackageJson(packagePath, scopeName) {
21936
21982
  lint: "tsc --noEmit"
21937
21983
  },
21938
21984
  dependencies: {
21939
- "@radix-ui/react-slot": "catalog:",
21985
+ ...uiFoundationDeps,
21940
21986
  "class-variance-authority": "catalog:",
21941
21987
  clsx: "catalog:",
21942
21988
  "tailwind-merge": "catalog:",
21943
- "iconoir-react": "catalog:",
21989
+ ...iconDeps,
21990
+ "tw-animate-css": "catalog:",
21991
+ shadcn: "catalog:",
21944
21992
  tailwindcss: "catalog:",
21945
21993
  "@tailwindcss/postcss": "catalog:",
21946
21994
  postcss: "catalog:"
@@ -28711,9 +28759,10 @@ function generateModernThemeCSS(baseColor, customRadius) {
28711
28759
  async function setupShadcnWeb(projectPath, context) {
28712
28760
  await ensureDirectory(join(projectPath, "src/components/ui"));
28713
28761
  await ensureDirectory(join(projectPath, "src/lib"));
28714
- const style = context.shadcnStyle || "new-york";
28762
+ const style = context.shadcnStyle || "radix-maia";
28715
28763
  const baseColor = context.shadcnBaseColor || "zinc";
28716
28764
  const radius = context.shadcnRadius || "0.625rem";
28765
+ const iconLibrary = context.shadcnIconLibrary || "iconoir";
28717
28766
  const componentsJson = {
28718
28767
  $schema: "https://ui.shadcn.com/schema.json",
28719
28768
  style,
@@ -28725,7 +28774,7 @@ async function setupShadcnWeb(projectPath, context) {
28725
28774
  baseColor,
28726
28775
  cssVariables: true
28727
28776
  },
28728
- iconLibrary: "lucide",
28777
+ iconLibrary,
28729
28778
  aliases: {
28730
28779
  components: "@/components",
28731
28780
  utils: "@/lib/utils",
@@ -28734,6 +28783,9 @@ async function setupShadcnWeb(projectPath, context) {
28734
28783
  hooks: "@/hooks"
28735
28784
  }
28736
28785
  };
28786
+ if (context.shadcnRtl) {
28787
+ componentsJson.rtl = true;
28788
+ }
28737
28789
  await writeFile(join(projectPath, "components.json"), JSON.stringify(componentsJson, null, 2));
28738
28790
  const utilsContent = `import { clsx, type ClassValue } from 'clsx';
28739
28791
  import { twMerge } from 'tailwind-merge';
@@ -28751,20 +28803,36 @@ export function cn(...inputs: ClassValue[]) {
28751
28803
  if (!packageJson.dependencies) {
28752
28804
  packageJson.dependencies = {};
28753
28805
  }
28754
- 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
+ }
28755
28812
  packageJson.dependencies["class-variance-authority"] = "catalog:";
28756
28813
  packageJson.dependencies.clsx = "catalog:";
28757
28814
  packageJson.dependencies["tailwind-merge"] = "catalog:";
28758
- packageJson.dependencies["lucide-react"] = "catalog:";
28815
+ const iconPackageName = iconLibrary === "iconoir" ? "iconoir-react" : iconLibrary === "phosphor" ? "@phosphor-icons/react" : "lucide-react";
28816
+ packageJson.dependencies[iconPackageName] = "catalog:";
28759
28817
  if (!packageJson.catalog) {
28760
28818
  packageJson.catalog = {};
28761
28819
  }
28762
- 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
+ }
28763
28825
  packageJson.catalog["class-variance-authority"] = "^0.7.1";
28764
28826
  packageJson.catalog.clsx = "^2.1.1";
28765
28827
  packageJson.catalog["tailwind-merge"] = "^3.4.0";
28766
- packageJson.catalog["lucide-react"] = "^0.562.0";
28767
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
+ }
28768
28836
  await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
28769
28837
  const globalsCssPath = join(projectPath, "src/app/globals.css");
28770
28838
  let globalsCss = "";
@@ -28791,9 +28859,11 @@ ${shadcnCss}`;
28791
28859
  async function setupShadcnMonorepo(projectPath, context) {
28792
28860
  const packageName = context.packageName || context.projectName.toLowerCase().replace(/\s+/g, "-");
28793
28861
  const uiPackageName = `@${packageName}/ui`;
28794
- const style = context.shadcnStyle || "new-york";
28862
+ const style = context.shadcnStyle || "radix-maia";
28795
28863
  const baseColor = context.shadcnBaseColor || "zinc";
28796
28864
  const radius = context.shadcnRadius || "0.625rem";
28865
+ const iconLibrary = context.shadcnIconLibrary || "iconoir";
28866
+ const shadcnBase = inferShadcnBase(context.shadcnStyle);
28797
28867
  await ensureDirectory(join(projectPath, "packages/ui/src/components"));
28798
28868
  await ensureDirectory(join(projectPath, "packages/ui/src/lib"));
28799
28869
  await ensureDirectory(join(projectPath, "packages/ui/src/hooks"));
@@ -28815,11 +28885,11 @@ async function setupShadcnMonorepo(projectPath, context) {
28815
28885
  "./components/ui/*": "./src/components/ui/*/index.ts"
28816
28886
  },
28817
28887
  dependencies: {
28818
- "@radix-ui/react-slot": "catalog:",
28888
+ ...shadcnBase === "base-ui" ? { "@base-ui/react": "catalog:" } : { "radix-ui": "catalog:" },
28819
28889
  "class-variance-authority": "catalog:",
28820
28890
  clsx: "catalog:",
28821
28891
  "tailwind-merge": "catalog:",
28822
- "lucide-react": "catalog:",
28892
+ ...iconLibrary === "iconoir" ? { "iconoir-react": "catalog:" } : iconLibrary === "phosphor" ? { "@phosphor-icons/react": "catalog:" } : { "lucide-react": "catalog:" },
28823
28893
  tailwindcss: "catalog:",
28824
28894
  "@tailwindcss/postcss": "catalog:"
28825
28895
  },
@@ -28841,7 +28911,7 @@ async function setupShadcnMonorepo(projectPath, context) {
28841
28911
  baseColor,
28842
28912
  cssVariables: true
28843
28913
  },
28844
- iconLibrary: "lucide",
28914
+ iconLibrary,
28845
28915
  aliases: {
28846
28916
  components: "./src/components",
28847
28917
  ui: "./src/components/ui",
@@ -28850,6 +28920,9 @@ async function setupShadcnMonorepo(projectPath, context) {
28850
28920
  lib: "@/lib"
28851
28921
  }
28852
28922
  };
28923
+ if (context.shadcnRtl) {
28924
+ uiComponentsJson.rtl = true;
28925
+ }
28853
28926
  await writeFile(join(projectPath, "packages/ui/components.json"), JSON.stringify(uiComponentsJson, null, 2));
28854
28927
  const uiUtilsContent = `import { clsx, type ClassValue } from 'clsx';
28855
28928
  import { twMerge } from 'tailwind-merge';
@@ -28939,7 +29012,7 @@ export default config;
28939
29012
  baseColor,
28940
29013
  cssVariables: true
28941
29014
  },
28942
- iconLibrary: "lucide",
29015
+ iconLibrary,
28943
29016
  aliases: {
28944
29017
  components: "@/components",
28945
29018
  hooks: "@/hooks",
@@ -28948,6 +29021,9 @@ export default config;
28948
29021
  ui: "@workspace/ui/components/ui"
28949
29022
  }
28950
29023
  };
29024
+ if (context.shadcnRtl) {
29025
+ appComponentsJson.rtl = true;
29026
+ }
28951
29027
  await writeFile(join(appPath, "components.json"), JSON.stringify(appComponentsJson, null, 2));
28952
29028
  const layoutPath = join(appPath, "src/app/layout.tsx");
28953
29029
  if (await fileExists(layoutPath)) {
@@ -29043,16 +29119,26 @@ export default nextConfig;
29043
29119
  rootPackageJson.catalog = {};
29044
29120
  }
29045
29121
  const shadcnDependencies = {
29046
- "@radix-ui/react-slot": "^1.2.4",
29047
29122
  "class-variance-authority": "^0.7.1",
29048
29123
  clsx: "^2.1.1",
29049
29124
  "tailwind-merge": "^3.4.0",
29050
- "lucide-react": "^0.562.0",
29051
29125
  "tw-animate-css": "^1.2.9",
29052
29126
  "@types/react": "^19.2.7",
29053
29127
  "@types/react-dom": "^19.2.3",
29054
29128
  typescript: "^5.9.3"
29055
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
+ }
29056
29142
  Object.assign(rootPackageJson.catalog, shadcnDependencies);
29057
29143
  await writeFile(rootPackageJsonPath, JSON.stringify(rootPackageJson, null, 2));
29058
29144
  await createShadcnDocs(join(projectPath, "packages/ui"), true, context);
@@ -29123,7 +29209,7 @@ async function setupTooling(projectPath, context) {
29123
29209
  extends: "../../tooling/typescript/base.json",
29124
29210
  compilerOptions: {
29125
29211
  target: "ESNext",
29126
- lib: ["ESNext"],
29212
+ lib: ["ESNext", "DOM", "DOM.Iterable"],
29127
29213
  module: "ESNext",
29128
29214
  moduleResolution: "bundler",
29129
29215
  declaration: true,
@@ -29447,11 +29533,15 @@ async function buildEnterprisePreset(projectPath, context) {
29447
29533
  autoprefixer: "^10.4.23",
29448
29534
  postcss: "^8.5.6",
29449
29535
  "@tailwindcss/postcss": "^4.1.18",
29536
+ "radix-ui": "^1.4.3",
29450
29537
  "@radix-ui/react-slot": "^1.2.4",
29538
+ "@base-ui/react": "^1.2.0",
29539
+ shadcn: "^3.8.5",
29451
29540
  "class-variance-authority": "^0.7.1",
29452
29541
  clsx: "^2.1.1",
29453
29542
  "tailwind-merge": "^3.4.0",
29454
29543
  "iconoir-react": "^7.11.0",
29544
+ "@phosphor-icons/react": "^2.1.10",
29455
29545
  "lucide-react": "^0.562.0",
29456
29546
  "tw-animate-css": "^1.2.9",
29457
29547
  "@biomejs/biome": "^2.3.10",
@@ -29624,9 +29714,11 @@ async function buildEnterprisePreset(projectPath, context) {
29624
29714
  const enterpriseContext = {
29625
29715
  ...context,
29626
29716
  uiLibrary: "shadcn",
29627
- shadcnStyle: context.shadcnStyle || "new-york",
29717
+ shadcnStyle: context.shadcnStyle || "radix-maia",
29628
29718
  shadcnBaseColor: context.shadcnBaseColor || "zinc",
29629
- shadcnRadius: context.shadcnRadius || "0.625rem"
29719
+ shadcnRadius: context.shadcnRadius || "0.625rem",
29720
+ shadcnIconLibrary: context.shadcnIconLibrary || "iconoir",
29721
+ shadcnRtl: context.shadcnRtl
29630
29722
  };
29631
29723
  await setupShadcnMonorepo(projectPath, enterpriseContext);
29632
29724
  }
@@ -29787,11 +29879,15 @@ async function buildFullPreset(projectPath, context) {
29787
29879
  autoprefixer: "^10.4.23",
29788
29880
  postcss: "^8.5.6",
29789
29881
  "@tailwindcss/postcss": "^4.1.18",
29882
+ "radix-ui": "^1.4.3",
29790
29883
  "@radix-ui/react-slot": "^1.2.4",
29884
+ "@base-ui/react": "^1.2.0",
29885
+ shadcn: "^3.8.5",
29791
29886
  "class-variance-authority": "^0.7.1",
29792
29887
  clsx: "^2.1.1",
29793
29888
  "tailwind-merge": "^3.4.0",
29794
29889
  "iconoir-react": "^7.11.0",
29890
+ "@phosphor-icons/react": "^2.1.10",
29795
29891
  "lucide-react": "^0.562.0",
29796
29892
  "tw-animate-css": "^1.2.9",
29797
29893
  ...context.codeQuality === "biome" ? { "@biomejs/biome": "^2.3.10" } : {},
@@ -30585,1046 +30681,1023 @@ networks:
30585
30681
  const fullContext = {
30586
30682
  ...context,
30587
30683
  uiLibrary: "shadcn",
30588
- shadcnStyle: context.shadcnStyle || "new-york",
30684
+ shadcnStyle: context.shadcnStyle || "radix-maia",
30589
30685
  shadcnBaseColor: context.shadcnBaseColor || "zinc",
30590
- shadcnRadius: context.shadcnRadius || "0.625rem"
30686
+ shadcnRadius: context.shadcnRadius || "0.625rem",
30687
+ shadcnIconLibrary: context.shadcnIconLibrary || "iconoir",
30688
+ shadcnRtl: context.shadcnRtl
30591
30689
  };
30592
30690
  await setupShadcnMonorepo(projectPath, fullContext);
30593
30691
  }
30594
30692
  await setupTooling(projectPath, context);
30595
30693
  await setupVSCodeDebug(projectPath, context, "full");
30596
30694
  }
30597
- // ../templates/src/builders/minimal.ts
30695
+ // ../templates/src/builders/full-v2.ts
30598
30696
  init_src();
30599
30697
  init_dist();
30600
- async function buildMinimalPreset(projectPath, context) {
30601
- const indexContent = `console.log('Hello from ${context.projectName}! \uD83C\uDF5E');
30602
-
30603
- // Your code here
30604
- const greet = (name: string): void => {
30605
- console.log(\`Welcome, \${name}!\`);
30606
- };
30698
+ init_package_json();
30607
30699
 
30608
- greet('Bun');
30609
- `;
30610
- await writeFile(join(projectPath, "src/index.ts"), indexContent);
30611
- 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",
30612
30720
  compilerOptions: {
30613
- lib: ["ESNext"],
30614
- target: "ESNext",
30615
- module: "ESNext",
30616
- moduleDetection: "force",
30617
30721
  jsx: "react-jsx",
30618
- allowJs: true,
30619
- moduleResolution: "bundler",
30620
- allowImportingTsExtensions: true,
30621
- verbatimModuleSyntax: true,
30622
- noEmit: true,
30623
- strict: true,
30624
- skipLibCheck: true,
30625
- noFallthroughCasesInSwitch: true,
30626
- noUnusedLocals: false,
30627
- noUnusedParameters: false,
30628
- noPropertyAccessFromIndexSignature: false,
30629
- 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"
30630
30751
  }
30631
30752
  };
30632
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
30633
- const bunfigContent = generateBunfigContent(context);
30634
- await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
30635
- if (context.codeQuality === "ultracite") {
30636
- await setupUltracite(projectPath, context);
30637
- } else {
30638
- await setupBiome(projectPath, context);
30753
+ if (useModernStyle) {
30754
+ componentsJson.menuColor = options.shadcnMenuColor || "default";
30755
+ componentsJson.menuAccent = options.shadcnMenuAccent || "subtle";
30756
+ componentsJson.registries = {};
30639
30757
  }
30640
- await setupVSCodeDebug(projectPath, context, "minimal");
30641
- await generateMinimalReadme(projectPath, context);
30642
- }
30643
- // ../templates/src/builders/monorepo-bun.ts
30644
- init_src();
30645
- init_dist();
30646
- var databaseSetupMap6 = {
30647
- "postgres-drizzle": setupPostgresDrizzle,
30648
- "postgres-prisma": setupPostgresPrisma,
30649
- "mysql-drizzle": setupMySQLDrizzle,
30650
- "mysql-prisma": setupMySQLPrisma,
30651
- supabase: setupSupabaseOnly,
30652
- "supabase-drizzle": setupSupabaseDrizzle,
30653
- "supabase-prisma": setupSupabasePrisma,
30654
- "sqlite-drizzle": setupSQLiteDrizzle,
30655
- "sqlite-prisma": setupSQLitePrisma,
30656
- none: async () => {}
30657
- };
30658
- async function buildMonorepoBunPreset(projectPath, context) {
30659
- await ensureDirectory(join(projectPath, "apps/web"));
30660
- await ensureDirectory(join(projectPath, "apps/api"));
30661
- await ensureDirectory(join(projectPath, "packages/types"));
30662
- await ensureDirectory(join(projectPath, "packages/utils"));
30663
- if (context.database && context.database !== "none") {
30664
- await ensureDirectory(join(projectPath, "packages/db"));
30758
+ if (options.shadcnRtl) {
30759
+ componentsJson.rtl = true;
30665
30760
  }
30666
- const rootPackageJson = {
30667
- name: `${context.packageName}-monorepo`,
30668
- version: "0.0.0",
30669
- private: true,
30670
- workspaces: ["apps/*", "packages/*"],
30671
- scripts: {
30672
- dev: 'bun run --filter "*" dev',
30673
- build: 'bun run --filter "*" build',
30674
- lint: context.codeQuality === "biome" ? "biome check ." : "ultracite check .",
30675
- format: context.codeQuality === "biome" ? "biome check --write ." : "ultracite fix .",
30676
- test: "bun test",
30677
- "dev:web": 'bun run --filter "@*/web" dev',
30678
- "dev:api": 'bun run --filter "@*/api" dev',
30679
- debug: "bun --inspect apps/api/src/index.ts",
30680
- "debug:brk": "bun --inspect-brk apps/api/src/index.ts",
30681
- "debug:wait": "bun --inspect-wait apps/api/src/index.ts"
30682
- },
30683
- devDependencies: {
30684
- "@types/bun": "latest",
30685
- typescript: "catalog:",
30686
- ...context.codeQuality === "biome" ? { "@biomejs/biome": "catalog:" } : {}
30687
- },
30688
- catalog: {
30689
- react: "^19.2.3",
30690
- "react-dom": "^19.2.3",
30691
- "drizzle-orm": "^0.45.1",
30692
- "drizzle-kit": "^0.31.8",
30693
- postgres: "^3.4.7",
30694
- "@supabase/supabase-js": "^2.88.0",
30695
- "@prisma/client": "^7.2.0",
30696
- prisma: "^7.2.0",
30697
- mysql2: "^3.16.0",
30698
- "better-auth": "^1.4.7",
30699
- "next-auth": "^4.24.13",
30700
- "@auth/drizzle-adapter": "^1.11.1",
30701
- tailwindcss: "^4.1.18",
30702
- autoprefixer: "^10.4.23",
30703
- postcss: "^8.5.6",
30704
- typescript: "^5.9.3",
30705
- "@types/react": "^19.2.7",
30706
- "@types/react-dom": "^19.2.3",
30707
- "@types/node": "^25.0.3"
30708
- }
30709
- };
30710
- await writeFile(join(projectPath, "package.json"), JSON.stringify(rootPackageJson, null, 2));
30711
- const apiPath = join(projectPath, "apps/api");
30712
- await setupBunServeNative(apiPath, context, true);
30713
- const apiPackageJson = {
30714
- name: `@${context.packageName}/api`,
30715
- version: "0.0.0",
30716
- private: true,
30717
- scripts: {
30718
- dev: "bun run --hot src/index.ts",
30719
- start: "bun run src/index.ts",
30720
- debug: "bun --inspect src/index.ts",
30721
- "debug:brk": "bun --inspect-brk src/index.ts",
30722
- "debug:wait": "bun --inspect-wait src/index.ts"
30723
- },
30724
- dependencies: {
30725
- [`@${context.packageName}/types`]: "workspace:*"
30726
- },
30727
- devDependencies: {
30728
- "@types/bun": "latest",
30729
- typescript: "catalog:"
30730
- }
30731
- };
30732
- await writeFile(join(apiPath, "package.json"), JSON.stringify(apiPackageJson, null, 2));
30733
- const webPath = join(projectPath, "apps/web");
30734
- await setupBunFullstack(webPath, context);
30735
- const webPackageJson = {
30736
- name: `@${context.packageName}/web`,
30737
- version: "0.0.0",
30738
- private: true,
30739
- scripts: {
30740
- dev: "bun run --hot src/index.ts",
30741
- build: "bun build src/index.ts --outdir ./dist --target bun",
30742
- start: "bun run dist/index.js",
30743
- debug: "bun --inspect src/index.ts",
30744
- "debug:brk": "bun --inspect-brk src/index.ts",
30745
- "debug:wait": "bun --inspect-wait src/index.ts"
30746
- },
30747
- dependencies: {
30748
- react: "catalog:",
30749
- "react-dom": "catalog:",
30750
- [`@${context.packageName}/types`]: "workspace:*"
30751
- },
30752
- devDependencies: {
30753
- "@types/react": "catalog:",
30754
- "@types/react-dom": "catalog:",
30755
- "@types/bun": "latest",
30756
- typescript: "catalog:"
30757
- }
30758
- };
30759
- await writeFile(join(webPath, "package.json"), JSON.stringify(webPackageJson, null, 2));
30760
- if (context.database && context.database !== "none") {
30761
- await databaseSetupMap6[context.database](join(projectPath, "packages/db"), context, true);
30762
- }
30763
- if (context.redis) {
30764
- await setupRedis(join(projectPath, "packages"), context, true);
30765
- }
30766
- if (context.auth === "better-auth") {
30767
- await setupBetterAuth(join(projectPath, "packages"), context, true);
30768
- } else if (context.auth === "nextauth") {
30769
- await setupNextAuth(join(projectPath, "packages"), context, true);
30770
- }
30771
- if (context.useBunSecrets) {
30772
- await setupBunSecrets(join(projectPath, "packages"), context, true);
30773
- }
30774
- if (context.codeQuality === "biome") {
30775
- 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
+ `;
30776
30787
  } else {
30777
- await setupUltracite(projectPath, context);
30778
- }
30779
- if (context.docker) {
30780
- await setupDocker(projectPath, context);
30781
- }
30782
- if (context.cicd) {
30783
- 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
+ `;
30784
30797
  }
30785
- await setupTooling(projectPath, context);
30786
- await setupVSCodeDebug(projectPath, context, "full");
30787
- const bunfigContent = generateBunfigContent(context);
30788
- await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
30789
- const typesPackageJson = {
30790
- name: `@${context.packageName}/types`,
30791
- version: "0.0.0",
30792
- private: true,
30793
- main: "./index.ts",
30794
- types: "./index.ts",
30795
- scripts: {},
30796
- devDependencies: {
30797
- typescript: "catalog:"
30798
- }
30799
- };
30800
- await ensureDirectory(join(projectPath, "packages/types"));
30801
- await writeFile(join(projectPath, "packages/types/package.json"), JSON.stringify(typesPackageJson, null, 2));
30802
- 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
+
30803
30874
  export interface User {
30804
- id: number;
30805
- name: string;
30875
+ id: string;
30806
30876
  email: string;
30877
+ name?: string;
30807
30878
  createdAt: Date;
30879
+ updatedAt: Date;
30808
30880
  }
30809
30881
 
30810
- export interface ApiResponse<T = unknown> {
30882
+ export interface ApiResponse<T> {
30811
30883
  success: boolean;
30812
30884
  data?: T;
30813
- error?: string;
30814
- timestamp: string;
30815
- }
30816
- `;
30817
- await writeFile(join(projectPath, "packages/types/index.ts"), typesIndex);
30818
- const utilsPackageJson = {
30819
- name: `@${context.packageName}/utils`,
30820
- version: "0.0.0",
30821
- private: true,
30822
- main: "./index.ts",
30823
- types: "./index.ts",
30824
- scripts: {},
30825
- dependencies: {},
30826
- devDependencies: {
30827
- typescript: "catalog:"
30828
- }
30885
+ error?: {
30886
+ code: string;
30887
+ message: string;
30829
30888
  };
30830
- await ensureDirectory(join(projectPath, "packages/utils"));
30831
- await writeFile(join(projectPath, "packages/utils/package.json"), JSON.stringify(utilsPackageJson, null, 2));
30832
- const utilsIndex = `// Shared utilities across the monorepo
30833
- export function formatDate(date: Date): string {
30834
- return date.toISOString();
30835
30889
  }
30836
30890
 
30837
- export function capitalize(str: string): string {
30838
- 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
+ };
30839
30898
  }
30840
- `;
30841
- await writeFile(join(projectPath, "packages/utils/index.ts"), utilsIndex);
30842
- await generateMonorepoReadme(projectPath, context, "bun");
30843
- const tsconfigContent = {
30844
- extends: "./tooling/typescript/base.json",
30845
- compilerOptions: {},
30846
- 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/**/*"],
30847
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);
30848
30988
  };
30849
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
30850
30989
  }
30851
- // ../templates/src/builders/web.ts
30852
- init_src();
30853
- init_dist();
30854
- async function buildWebPreset(projectPath, context) {
30855
- await ensureDirectory(join(projectPath, "src/app"));
30856
- await ensureDirectory(join(projectPath, "public"));
30857
- const layoutContent = `import type { Metadata } from 'next'
30858
- 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';
30859
31051
 
30860
31052
  export const metadata: Metadata = {
30861
31053
  title: '${context.projectName}',
30862
- description: 'Built with bunkit \uD83C\uDF5E',
30863
- }
31054
+ description: 'Enterprise SaaS built with bunkit',
31055
+ };
30864
31056
 
30865
31057
  export default function RootLayout({
30866
31058
  children,
30867
31059
  }: {
30868
- children: React.ReactNode
31060
+ children: React.ReactNode;
30869
31061
  }) {
30870
31062
  return (
30871
31063
  <html lang="en">
30872
- <body>{children}</body>
31064
+ <body className="antialiased">{children}</body>
30873
31065
  </html>
30874
- )
31066
+ );
30875
31067
  }
30876
- `;
30877
- await writeFile(join(projectPath, "src/app/layout.tsx"), layoutContent);
30878
- 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() {
30879
31072
  return (
30880
- <main className="min-h-screen flex items-center justify-center">
30881
- <div className="text-center">
30882
- <h1 className="text-4xl font-bold mb-4">
30883
- 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}
30884
31080
  </h1>
30885
- <p className="text-gray-600">
30886
- 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
30887
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>
30888
31100
  </div>
30889
31101
  </main>
30890
- )
31102
+ );
30891
31103
  }
30892
-
30893
- // Next.js 16 Note:
30894
- // When you add dynamic routes with params, make your component async and await params:
30895
- // export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
30896
- // const { slug } = await params;
30897
- // return <div>{slug}</div>;
30898
- // }
30899
- `;
30900
- await writeFile(join(projectPath, "src/app/page.tsx"), pageContent);
30901
- const globalsCssContent = `@import "tailwindcss";
30902
- `;
30903
- await writeFile(join(projectPath, "src/app/globals.css"), globalsCssContent);
30904
- const nextConfigContent = `import type { NextConfig } from 'next';
31104
+ `);
31105
+ await writeFile(join(projectPath, "apps/web/next.config.ts"), `import type { NextConfig } from 'next';
30905
31106
 
30906
31107
  const nextConfig: NextConfig = {
30907
- /* config options here */
31108
+ transpilePackages: ['@${scopeName}/ui'],
30908
31109
  };
30909
31110
 
30910
31111
  export default nextConfig;
30911
- `;
30912
- await writeFile(join(projectPath, "next.config.ts"), nextConfigContent);
30913
- const tailwindConfigContent = `import type { Config } from 'tailwindcss';
30914
-
30915
- const config: Config = {
30916
- content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
30917
- theme: {
30918
- extend: {},
30919
- },
30920
- plugins: [],
30921
- };
30922
-
30923
- export default config;
30924
- `;
30925
- await writeFile(join(projectPath, "tailwind.config.ts"), tailwindConfigContent);
30926
- const getTsCompilerOptions = () => {
30927
- const baseOptions = {
30928
- target: "ES2017",
30929
- lib: ["dom", "dom.iterable", "esnext"],
30930
- allowJs: true,
30931
- skipLibCheck: true,
30932
- noEmit: true,
30933
- esModuleInterop: true,
30934
- module: "esnext",
30935
- moduleResolution: "bundler",
30936
- resolveJsonModule: true,
30937
- isolatedModules: true,
30938
- jsx: "react-jsx",
30939
- incremental: true,
30940
- plugins: [{ name: "next" }],
30941
- paths: context.pathAliases ? { "@/*": ["./src/*"] } : undefined
30942
- };
30943
- if (context.tsStrictness === "strict") {
30944
- return {
30945
- ...baseOptions,
30946
- strict: true,
30947
- noUnusedLocals: true,
30948
- noUnusedParameters: true,
30949
- noFallthroughCasesInSwitch: true,
30950
- noImplicitReturns: true
30951
- };
30952
- }
30953
- if (context.tsStrictness === "moderate") {
30954
- return {
30955
- ...baseOptions,
30956
- strict: true,
30957
- noUnusedLocals: false,
30958
- noUnusedParameters: false
30959
- };
30960
- }
30961
- return {
30962
- ...baseOptions,
30963
- strict: false,
30964
- noImplicitAny: false
30965
- };
30966
- };
30967
- const tsconfigContent = {
30968
- compilerOptions: getTsCompilerOptions(),
30969
- include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/dev/types/**/*.ts"],
30970
- exclude: ["node_modules"]
30971
- };
30972
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
30973
- const bunfigContent = generateBunfigContent(context);
30974
- await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
30975
- if (context.codeQuality === "ultracite") {
30976
- await setupUltracite(projectPath, context);
30977
- } else {
30978
- await setupBiome(projectPath, context);
30979
- }
30980
- if (context.docker) {
30981
- await setupDocker(projectPath, context);
30982
- }
30983
- if (context.cicd) {
30984
- await setupGitHubActions(projectPath, context);
30985
- }
30986
- if (context.uiLibrary === "shadcn" && context.cssFramework === "tailwind") {
30987
- await setupShadcnWeb(projectPath, context);
30988
- }
30989
- await setupVSCodeDebug(projectPath, context, "web");
30990
- const packageJsonPath = join(projectPath, "package.json");
30991
- const existingPackageJson = JSON.parse(await Bun.file(packageJsonPath).text());
30992
- existingPackageJson.dependencies = {
30993
- react: "^19.2.3",
30994
- "react-dom": "^19.2.3",
30995
- next: "^16.0.10",
30996
- ...existingPackageJson.dependencies
30997
- };
30998
- existingPackageJson.devDependencies = {
30999
- ...existingPackageJson.devDependencies,
31000
- "@types/react": "^19.2.7",
31001
- "@types/react-dom": "^19.2.3",
31002
- "@types/node": "^25.0.3"
31003
- };
31004
- if (!existingPackageJson.scripts) {
31005
- existingPackageJson.scripts = {};
31006
- }
31007
- if (!existingPackageJson.scripts.dev) {
31008
- existingPackageJson.scripts.dev = "bun run --bun next dev --turbopack";
31009
- }
31010
- if (!existingPackageJson.scripts.build) {
31011
- existingPackageJson.scripts.build = "bun run --bun next build";
31012
- }
31013
- if (!existingPackageJson.scripts.start) {
31014
- existingPackageJson.scripts.start = "bun run --bun next start";
31015
- }
31016
- if (!existingPackageJson.scripts.debug) {
31017
- existingPackageJson.scripts.debug = "bun --inspect node_modules/.bin/next dev --turbopack";
31018
- }
31019
- if (!existingPackageJson.scripts["debug:brk"]) {
31020
- existingPackageJson.scripts["debug:brk"] = "bun --inspect-brk node_modules/.bin/next dev --turbopack";
31021
- }
31022
- if (!existingPackageJson.scripts["debug:wait"]) {
31023
- existingPackageJson.scripts["debug:wait"] = "bun --inspect-wait node_modules/.bin/next dev --turbopack";
31024
- }
31025
- await writeFile(packageJsonPath, JSON.stringify(existingPackageJson, null, 2));
31026
- await generateNextjsReadme(projectPath, context);
31027
- }
31028
- // ../templates/src/builders/full-v2.ts
31029
- init_src();
31030
- init_dist();
31031
- init_package_json();
31032
-
31033
- // ../templates/src/shared/ui-package.ts
31034
- init_src();
31035
- init_dist();
31036
- init_package_json();
31037
- function isModernStyle(style) {
31038
- return style === "radix-maia" || style === "radix-vega" || style === "radix-nova" || style === "radix-lyra" || style === "radix-mira";
31039
- }
31040
- async function buildUiPackage(packagesPath, options) {
31041
- const uiPath = join(packagesPath, "ui");
31042
- await ensureDirectory(join(uiPath, "src/components"));
31043
- await ensureDirectory(join(uiPath, "src/hooks"));
31044
- await ensureDirectory(join(uiPath, "src/lib"));
31045
- await ensureDirectory(join(uiPath, "src/styles"));
31046
- await writeUiPackageJson(uiPath, options.scopeName);
31047
- await writeFile(join(uiPath, "tsconfig.json"), JSON.stringify({
31048
- extends: "../../tooling/typescript/library.json",
31112
+ `);
31113
+ await writeFile(join(projectPath, "apps/web/tsconfig.json"), JSON.stringify({
31114
+ extends: "../../tooling/typescript/nextjs.json",
31049
31115
  compilerOptions: {
31050
- jsx: "react-jsx",
31051
- rootDir: "./src",
31052
- outDir: "./dist",
31053
- baseUrl: ".",
31054
31116
  paths: {
31055
31117
  "@/*": ["./src/*"]
31056
31118
  }
31057
31119
  },
31058
- include: ["src/**/*"],
31120
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
31059
31121
  exclude: ["node_modules"]
31060
31122
  }, null, 2));
31061
- const style = options.shadcnStyle || "radix-maia";
31062
- const useModernStyle = isModernStyle(style);
31063
- const iconLibrary = options.shadcnIconLibrary || (useModernStyle ? "phosphor" : "iconoir");
31064
- const componentsJson = {
31065
- $schema: "https://ui.shadcn.com/schema.json",
31066
- style,
31067
- rsc: true,
31068
- tsx: true,
31069
- tailwind: {
31070
- config: "",
31071
- css: "./src/styles/globals.css",
31072
- baseColor: options.shadcnBaseColor || "zinc",
31073
- cssVariables: true,
31074
- prefix: ""
31075
- },
31076
- iconLibrary,
31077
- aliases: {
31078
- components: "@/components",
31079
- utils: "@/lib/utils",
31080
- ui: "@/components/ui",
31081
- lib: "@/lib",
31082
- hooks: "@/hooks"
31083
- }
31084
- };
31085
- if (useModernStyle) {
31086
- componentsJson.menuColor = options.shadcnMenuColor || "default";
31087
- componentsJson.menuAccent = options.shadcnMenuAccent || "subtle";
31088
- componentsJson.registries = {};
31089
- }
31090
- await writeFile(join(uiPath, "components.json"), JSON.stringify(componentsJson, null, 2));
31091
- await writeFile(join(uiPath, "postcss.config.mjs"), `export default {
31123
+ await writeFile(join(projectPath, "apps/web/postcss.config.mjs"), `export default {
31092
31124
  plugins: {
31093
31125
  '@tailwindcss/postcss': {},
31094
31126
  },
31095
31127
  };
31096
31128
  `);
31097
- const appsToScan = options.appsToScan || ["web", "platform"];
31098
- const appSourcePaths = appsToScan.map((app) => `@source "../../../apps/${app}/src/**/*.{ts,tsx}";`);
31099
- const baseColor = options.shadcnBaseColor || "zinc";
31100
- const customRadius = options.shadcnRadius || "0.625rem";
31101
- let globalsCss;
31102
- if (useModernStyle) {
31103
- const modernThemeCSS = generateModernThemeCSS(baseColor, customRadius);
31104
- globalsCss = `@import "tailwindcss";
31105
- @import "tw-animate-css";
31106
- @import "shadcn/tailwind.css";
31107
- ${appSourcePaths.join(`
31108
- `)}
31109
- @source "../**/*.{ts,tsx}";
31110
-
31111
- @custom-variant dark (&:is(.dark *));
31112
-
31113
- ${modernThemeCSS}
31114
- `;
31115
- } else {
31116
- const theme = themes[baseColor] || themes.zinc;
31117
- const themeCSS = generateThemeCSS(theme, customRadius);
31118
- globalsCss = `@import "tailwindcss";
31119
- ${appSourcePaths.join(`
31120
- `)}
31121
- @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';
31122
31136
 
31123
- ${themeCSS}
31124
- `;
31125
- }
31126
- await writeFile(join(uiPath, "src/styles/globals.css"), globalsCss);
31127
- await writeFile(join(uiPath, "src/lib/utils.ts"), `import { clsx, type ClassValue } from 'clsx';
31128
- import { twMerge } from 'tailwind-merge';
31137
+ export const metadata: Metadata = {
31138
+ title: '${context.projectName} - Admin',
31139
+ description: 'Admin dashboard for ${context.projectName}',
31140
+ };
31129
31141
 
31130
- export function cn(...inputs: ClassValue[]) {
31131
- return twMerge(clsx(inputs));
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
+ );
31132
31152
  }
31133
31153
  `);
31134
- await writeFile(join(uiPath, "src/components/index.ts"), `/**
31135
- * UI Components
31136
- *
31137
- * Add shadcn/ui components using:
31138
- * bunx shadcn@latest add button card dialog ...
31139
- *
31140
- * Then export them here for use across apps.
31141
- */
31142
-
31143
- // Export components as they are added
31144
- // Example: export { Button } from './ui/button';
31145
-
31146
- // Empty export to make this a valid ES module
31147
- export {};
31148
- `);
31149
- await writeFile(join(uiPath, "src/hooks/index.ts"), `/**
31150
- * Shared Hooks
31151
- *
31152
- * Custom React hooks shared across apps.
31153
- */
31154
+ await writeFile(join(projectPath, "apps/platform/src/app/page.tsx"), `import { ViewGrid, User, Dollar, GraphUp } from 'iconoir-react';
31154
31155
 
31155
- // Export hooks as they are added
31156
- // Example: export { useMediaQuery } from './use-media-query';
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>
31157
31169
 
31158
- // Empty export to make this a valid ES module
31159
- export {};
31160
- `);
31161
- await writeFile(join(uiPath, "src/index.ts"), `/**
31162
- * @${options.scopeName}/ui - Shared UI Components
31163
- *
31164
- * This package provides:
31165
- * - shadcn/ui components (add with: bunx shadcn@latest add <component>)
31166
- * - Tailwind CSS v4 configuration
31167
- * - Shared hooks and utilities
31168
- *
31169
- * Usage in apps:
31170
- * - Import globals.css: import '@${options.scopeName}/ui/globals.css';
31171
- * - Import components: import { Button } from '@${options.scopeName}/ui/components';
31172
- * - Import utils: import { cn } from '@${options.scopeName}/ui/lib/utils';
31173
- */
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
+ );
31196
+ }
31197
+ `);
31198
+ await writeFile(join(projectPath, "apps/platform/next.config.ts"), `import type { NextConfig } from 'next';
31174
31199
 
31175
- export * from './lib/utils';
31176
- export * from './hooks';
31177
- export * from './components';
31200
+ const nextConfig: NextConfig = {
31201
+ transpilePackages: ['@${scopeName}/ui'],
31202
+ };
31203
+
31204
+ export default nextConfig;
31178
31205
  `);
31179
- }
31180
- async function buildTypesPackage(packagesPath, scopeName) {
31181
- const typesPath = join(packagesPath, "types");
31182
- await ensureDirectory(join(typesPath, "src"));
31183
- const { writeTypesPackageJson: writeTypesPackageJson2 } = await Promise.resolve().then(() => (init_package_json(), exports_package_json));
31184
- await writeTypesPackageJson2(typesPath, scopeName);
31185
- await writeFile(join(typesPath, "tsconfig.json"), JSON.stringify({
31186
- extends: "../../tooling/typescript/library.json",
31206
+ await writeFile(join(projectPath, "apps/platform/tsconfig.json"), JSON.stringify({
31207
+ extends: "../../tooling/typescript/nextjs.json",
31187
31208
  compilerOptions: {
31188
- rootDir: "./src",
31189
- outDir: "./dist"
31209
+ paths: {
31210
+ "@/*": ["./src/*"]
31211
+ }
31190
31212
  },
31191
- include: ["src/**/*"],
31213
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
31192
31214
  exclude: ["node_modules"]
31193
31215
  }, null, 2));
31194
- await writeFile(join(typesPath, "src/index.ts"), `/**
31195
- * @${scopeName}/types - Shared TypeScript Types
31196
- *
31197
- * Define types that are shared across multiple packages/apps here.
31198
- */
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';
31199
31228
 
31200
- // Example types - replace with your actual types
31229
+ const app = new Hono();
31201
31230
 
31202
- export interface User {
31203
- id: string;
31204
- email: string;
31205
- name?: string;
31206
- createdAt: Date;
31207
- updatedAt: Date;
31208
- }
31231
+ // Middleware
31232
+ app.use('*', logger());
31233
+ app.use('*', cors());
31209
31234
 
31210
- export interface ApiResponse<T> {
31211
- success: boolean;
31212
- data?: T;
31213
- error?: {
31214
- code: string;
31215
- message: string;
31216
- };
31217
- }
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
+ });
31218
31243
 
31219
- export interface PaginatedResponse<T> extends ApiResponse<T[]> {
31220
- pagination: {
31221
- page: number;
31222
- perPage: number;
31223
- total: number;
31224
- totalPages: number;
31225
- };
31226
- }
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;
31227
31280
  `);
31228
- }
31229
- async function buildUtilsPackage(packagesPath, scopeName) {
31230
- const utilsPath = join(packagesPath, "utils");
31231
- await ensureDirectory(join(utilsPath, "src"));
31232
- const { writeUtilsPackageJson: writeUtilsPackageJson2 } = await Promise.resolve().then(() => (init_package_json(), exports_package_json));
31233
- await writeUtilsPackageJson2(utilsPath, scopeName);
31234
- await writeFile(join(utilsPath, "tsconfig.json"), JSON.stringify({
31235
- extends: "../../tooling/typescript/library.json",
31281
+ await writeFile(join(projectPath, "apps/api/tsconfig.json"), JSON.stringify({
31282
+ extends: "../../tooling/typescript/api.json",
31236
31283
  compilerOptions: {
31237
- rootDir: "./src",
31238
- outDir: "./dist"
31284
+ types: ["bun-types"]
31239
31285
  },
31240
31286
  include: ["src/**/*"],
31241
31287
  exclude: ["node_modules"]
31242
31288
  }, null, 2));
31243
- await writeFile(join(utilsPath, "src/index.ts"), `/**
31244
- * @${scopeName}/utils - Shared Utilities
31245
- *
31246
- * Utility functions shared across packages and apps.
31247
- */
31248
-
31249
- /**
31250
- * Format a date to a human-readable string
31251
- */
31252
- export function formatDate(date: Date | string, locale = 'en-US'): string {
31253
- const d = typeof date === 'string' ? new Date(date) : date;
31254
- return d.toLocaleDateString(locale, {
31255
- year: 'numeric',
31256
- month: 'long',
31257
- 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"]
31258
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");
31259
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');
31260
31328
 
31261
- /**
31262
- * Format a number as currency
31263
- */
31264
- export function formatCurrency(
31265
- amount: number,
31266
- currency = 'USD',
31267
- locale = 'en-US'
31268
- ): string {
31269
- return new Intl.NumberFormat(locale, {
31270
- style: 'currency',
31271
- currency,
31272
- }).format(amount);
31273
- }
31274
-
31275
- /**
31276
- * Sleep for a specified number of milliseconds
31277
- */
31278
- export function sleep(ms: number): Promise<void> {
31279
- return new Promise((resolve) => setTimeout(resolve, ms));
31280
- }
31329
+ // Your code here
31330
+ const greet = (name: string): void => {
31331
+ console.log(\`Welcome, \${name}!\`);
31332
+ };
31281
31333
 
31282
- /**
31283
- * Safely parse JSON with a fallback value
31284
- */
31285
- export function safeJsonParse<T>(json: string, fallback: T): T {
31286
- try {
31287
- return JSON.parse(json) as T;
31288
- } catch {
31289
- 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);
31290
31365
  }
31366
+ await setupVSCodeDebug(projectPath, context, "minimal");
31367
+ await generateMinimalReadme(projectPath, context);
31291
31368
  }
31292
-
31293
- /**
31294
- * Generate a random ID
31295
- */
31296
- export function generateId(length = 12): string {
31297
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
31298
- let result = '';
31299
- for (let i = 0; i < length; i++) {
31300
- 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"));
31301
31391
  }
31302
- return result;
31303
- }
31304
-
31305
- /**
31306
- * Debounce a function
31307
- */
31308
- export function debounce<T extends (...args: unknown[]) => unknown>(
31309
- func: T,
31310
- wait: number
31311
- ): (...args: Parameters<T>) => void {
31312
- let timeout: ReturnType<typeof setTimeout> | null = null;
31313
- return (...args: Parameters<T>) => {
31314
- if (timeout) clearTimeout(timeout);
31315
- 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
+ }
31316
31435
  };
31317
- }
31318
-
31319
- /**
31320
- * Check if running on server
31321
- * Uses a type-safe approach that works without DOM types
31322
- */
31323
- export function isServer(): boolean {
31324
- return !(typeof window !== 'undefined' && window.document);
31325
- }
31326
-
31327
- /**
31328
- * Check if running on client
31329
- * Uses a type-safe approach that works without DOM types
31330
- */
31331
- export function isClient(): boolean {
31332
- return typeof window !== 'undefined' && !!window.document;
31333
- }
31334
-
31335
- // Declare window for environments without DOM types
31336
- declare const window: { document?: unknown } | undefined;
31337
- `);
31338
- }
31339
-
31340
- // ../templates/src/builders/full-v2.ts
31341
- async function buildFullPresetV2(projectPath, context) {
31342
- const preset = PresetRegistry.get("nextjs-monorepo");
31343
- if (!preset) {
31344
- 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);
31345
31488
  }
31346
- const scopeName = context.packageName;
31347
- const directories = [
31348
- "apps/web/src/app",
31349
- "apps/web/src/components",
31350
- "apps/web/src/lib",
31351
- "apps/platform/src/app",
31352
- "apps/platform/src/components",
31353
- "apps/platform/src/lib",
31354
- "apps/api/src/routes",
31355
- "apps/api/src/middleware",
31356
- "packages/ui",
31357
- "packages/types",
31358
- "packages/utils",
31359
- "tooling/typescript"
31360
- ];
31361
- for (const dir of directories) {
31362
- await ensureDirectory(join(projectPath, dir));
31489
+ if (context.redis) {
31490
+ await setupRedis(join(projectPath, "packages"), context, true);
31363
31491
  }
31364
- const catalog = preset.catalogEntries || {};
31365
- await writeMonorepoRootPackageJson(projectPath, scopeName, catalog, {
31366
- hasWeb: true,
31367
- hasPlatform: true,
31368
- hasApi: true,
31369
- codeQuality: context.codeQuality === "biome" ? "biome" : "ultracite"
31370
- });
31371
- await writeFile(join(projectPath, "bunfig.toml"), generateBunfigContent(context));
31372
- await writeNextjsAppPackageJson(join(projectPath, "apps/web"), scopeName, "web", { usesUi: true, usesTypes: true });
31373
- await writeFile(join(projectPath, "apps/web/src/app/layout.tsx"), `import type { Metadata } from 'next';
31374
- import '@${scopeName}/ui/globals.css';
31375
-
31376
- export const metadata: Metadata = {
31377
- title: '${context.projectName}',
31378
- description: 'Enterprise SaaS built with bunkit',
31379
- };
31380
-
31381
- export default function RootLayout({
31382
- children,
31383
- }: {
31384
- children: React.ReactNode;
31385
- }) {
31386
- return (
31387
- <html lang="en">
31388
- <body className="antialiased">{children}</body>
31389
- </html>
31390
- );
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;
31391
31534
  }
31392
- `);
31393
- await writeFile(join(projectPath, "apps/web/src/app/page.tsx"), `import { Home, Rocket, Book } from 'iconoir-react';
31394
31535
 
31395
- export default function HomePage() {
31396
- return (
31397
- <main className="min-h-screen flex items-center justify-center bg-gradient-to-br from-background to-muted">
31398
- <div className="text-center space-y-8 p-8 max-w-3xl">
31399
- <div className="flex justify-center">
31400
- <Home className="w-16 h-16 text-primary" />
31401
- </div>
31402
- <h1 className="text-5xl font-bold tracking-tight">
31403
- Welcome to ${context.projectName}
31404
- </h1>
31405
- <p className="text-xl text-muted-foreground">
31406
- Enterprise-grade SaaS built with Next.js 16, React 19, Hono, and Bun
31407
- </p>
31408
- <div className="flex gap-4 justify-center pt-4">
31409
- <a
31410
- href="/dashboard"
31411
- 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"
31412
- >
31413
- <Rocket className="w-5 h-5" />
31414
- Get Started
31415
- </a>
31416
- <a
31417
- href="/docs"
31418
- 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"
31419
- >
31420
- <Book className="w-5 h-5" />
31421
- Documentation
31422
- </a>
31423
- </div>
31424
- </div>
31425
- </main>
31426
- );
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();
31427
31561
  }
31428
- `);
31429
- await writeFile(join(projectPath, "apps/web/next.config.ts"), `import type { NextConfig } from 'next';
31430
-
31431
- const nextConfig: NextConfig = {
31432
- transpilePackages: ['@${scopeName}/ui'],
31433
- };
31434
31562
 
31435
- export default nextConfig;
31436
- `);
31437
- await writeFile(join(projectPath, "apps/web/tsconfig.json"), JSON.stringify({
31438
- extends: "../../tooling/typescript/nextjs.json",
31439
- compilerOptions: {
31440
- paths: {
31441
- "@/*": ["./src/*"]
31442
- }
31443
- },
31444
- 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/*/**/*"],
31445
31573
  exclude: ["node_modules"]
31446
- }, null, 2));
31447
- await writeFile(join(projectPath, "apps/web/postcss.config.mjs"), `export default {
31448
- plugins: {
31449
- '@tailwindcss/postcss': {},
31450
- },
31451
- };
31452
- `);
31453
- await writeNextjsAppPackageJson(join(projectPath, "apps/platform"), scopeName, "platform", { usesUi: true, usesTypes: true });
31454
- await writeFile(join(projectPath, "apps/platform/src/app/layout.tsx"), `import type { Metadata } from 'next';
31455
- 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'
31456
31585
 
31457
31586
  export const metadata: Metadata = {
31458
- title: '${context.projectName} - Admin',
31459
- description: 'Admin dashboard for ${context.projectName}',
31460
- };
31587
+ title: '${context.projectName}',
31588
+ description: 'Built with bunkit \uD83C\uDF5E',
31589
+ }
31461
31590
 
31462
31591
  export default function RootLayout({
31463
31592
  children,
31464
31593
  }: {
31465
- children: React.ReactNode;
31594
+ children: React.ReactNode
31466
31595
  }) {
31467
31596
  return (
31468
31597
  <html lang="en">
31469
- <body className="antialiased">{children}</body>
31598
+ <body>{children}</body>
31470
31599
  </html>
31471
- );
31600
+ )
31472
31601
  }
31473
- `);
31474
- await writeFile(join(projectPath, "apps/platform/src/app/page.tsx"), `import { ViewGrid, User, Dollar, GraphUp } from 'iconoir-react';
31475
-
31476
- export default function DashboardPage() {
31602
+ `;
31603
+ await writeFile(join(projectPath, "src/app/layout.tsx"), layoutContent);
31604
+ const pageContent = `export default function Home() {
31477
31605
  return (
31478
- <main className="min-h-screen bg-background">
31479
- <div className="max-w-7xl mx-auto py-12 px-4">
31480
- <header className="mb-8 flex items-center gap-3">
31481
- <ViewGrid className="w-8 h-8 text-primary" />
31482
- <div>
31483
- <h1 className="text-3xl font-bold">Admin Dashboard</h1>
31484
- <p className="text-muted-foreground">
31485
- Manage your ${context.projectName} platform
31486
- </p>
31487
- </div>
31488
- </header>
31489
-
31490
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
31491
- <div className="bg-card p-6 rounded-lg border shadow-sm">
31492
- <div className="flex items-center gap-3 mb-2">
31493
- <User className="w-5 h-5 text-blue-500" />
31494
- <h2 className="text-lg font-semibold">Users</h2>
31495
- </div>
31496
- <p className="text-3xl font-bold">1,234</p>
31497
- </div>
31498
- <div className="bg-card p-6 rounded-lg border shadow-sm">
31499
- <div className="flex items-center gap-3 mb-2">
31500
- <Dollar className="w-5 h-5 text-green-500" />
31501
- <h2 className="text-lg font-semibold">Revenue</h2>
31502
- </div>
31503
- <p className="text-3xl font-bold">$12,345</p>
31504
- </div>
31505
- <div className="bg-card p-6 rounded-lg border shadow-sm">
31506
- <div className="flex items-center gap-3 mb-2">
31507
- <GraphUp className="w-5 h-5 text-purple-500" />
31508
- <h2 className="text-lg font-semibold">Active</h2>
31509
- </div>
31510
- <p className="text-3xl font-bold">567</p>
31511
- </div>
31512
- </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>
31513
31614
  </div>
31514
31615
  </main>
31515
- );
31516
- }
31517
- `);
31518
- await writeFile(join(projectPath, "apps/platform/next.config.ts"), `import type { NextConfig } from 'next';
31519
-
31520
- const nextConfig: NextConfig = {
31521
- transpilePackages: ['@${scopeName}/ui'],
31522
- };
31523
-
31524
- export default nextConfig;
31525
- `);
31526
- await writeFile(join(projectPath, "apps/platform/tsconfig.json"), JSON.stringify({
31527
- extends: "../../tooling/typescript/nextjs.json",
31528
- compilerOptions: {
31529
- paths: {
31530
- "@/*": ["./src/*"]
31531
- }
31532
- },
31533
- include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
31534
- exclude: ["node_modules"]
31535
- }, null, 2));
31536
- await writeFile(join(projectPath, "apps/platform/postcss.config.mjs"), `export default {
31537
- plugins: {
31538
- '@tailwindcss/postcss': {},
31539
- },
31540
- };
31541
- `);
31542
- await writeHonoApiPackageJson(join(projectPath, "apps/api"), scopeName, {
31543
- usesTypes: true
31544
- });
31545
- await writeFile(join(projectPath, "apps/api/src/index.ts"), `import { Hono } from 'hono';
31546
- import { logger } from 'hono/logger';
31547
- import { cors } from 'hono/cors';
31548
-
31549
- const app = new Hono();
31550
-
31551
- // Middleware
31552
- app.use('*', logger());
31553
- app.use('*', cors());
31554
-
31555
- // Routes
31556
- app.get('/', (context) => {
31557
- return context.json({
31558
- name: '${context.projectName} API',
31559
- version: '1.0.0',
31560
- timestamp: new Date().toISOString(),
31561
- });
31562
- });
31563
-
31564
- app.get('/health', (context) => {
31565
- return context.json({ status: 'ok' });
31566
- });
31567
-
31568
- app.get('/api/users', (context) => {
31569
- return context.json({
31570
- users: [
31571
- { id: '1', email: 'john@example.com', name: 'John Doe' },
31572
- { id: '2', email: 'jane@example.com', name: 'Jane Smith' },
31573
- ],
31574
- });
31575
- });
31616
+ )
31617
+ }
31576
31618
 
31577
- // 404 handler
31578
- app.notFound((context) => {
31579
- return context.json({ error: 'Not found' }, 404);
31580
- });
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';
31581
31631
 
31582
- // Error handler
31583
- app.onError((error, context) => {
31584
- console.error('Error:', error);
31585
- return context.json({ error: 'Internal server error' }, 500);
31586
- });
31632
+ const nextConfig: NextConfig = {
31633
+ /* config options here */
31634
+ };
31587
31635
 
31588
- // Start server with HMR
31589
- const server = Bun.serve({
31590
- fetch: app.fetch,
31591
- port: 3002,
31592
- development: {
31593
- hmr: true,
31594
- },
31595
- });
31636
+ export default nextConfig;
31637
+ `;
31638
+ await writeFile(join(projectPath, "next.config.ts"), nextConfigContent);
31639
+ const tailwindConfigContent = `import type { Config } from 'tailwindcss';
31596
31640
 
31597
- 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
+ };
31598
31648
 
31599
- export default app;
31600
- `);
31601
- await writeFile(join(projectPath, "apps/api/tsconfig.json"), JSON.stringify({
31602
- extends: "../../tooling/typescript/api.json",
31603
- compilerOptions: {
31604
- types: ["bun-types"]
31605
- },
31606
- 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"],
31607
31696
  exclude: ["node_modules"]
31608
- }, null, 2));
31609
- await buildUiPackage(join(projectPath, "packages"), {
31610
- scopeName,
31611
- shadcnStyle: context.shadcnStyle || "radix-maia",
31612
- shadcnBase: context.shadcnBase || "radix",
31613
- shadcnBaseColor: context.shadcnBaseColor || "zinc",
31614
- shadcnIconLibrary: context.shadcnIconLibrary || "phosphor",
31615
- shadcnMenuAccent: context.shadcnMenuAccent || "subtle",
31616
- shadcnMenuColor: context.shadcnMenuColor || "default",
31617
- shadcnRadius: context.shadcnRadius || "0.625rem",
31618
- appsToScan: ["web", "platform"]
31619
- });
31620
- await buildTypesPackage(join(projectPath, "packages"), scopeName);
31621
- await buildUtilsPackage(join(projectPath, "packages"), scopeName);
31622
- await setupTooling(projectPath, context);
31623
- await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify({
31624
- extends: "./tooling/typescript/base.json",
31625
- include: ["apps/*/src/**/*", "packages/*/src/**/*"],
31626
- exclude: ["node_modules", "**/node_modules", "**/.next"]
31627
- }, 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);
31628
31701
  if (context.codeQuality === "ultracite") {
31629
31702
  await setupUltracite(projectPath, context);
31630
31703
  } else {
@@ -31636,13 +31709,52 @@ export default app;
31636
31709
  if (context.cicd) {
31637
31710
  await setupGitHubActions(projectPath, context);
31638
31711
  }
31639
- await setupVSCodeDebug(projectPath, context, "full");
31640
- 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);
31641
31753
  }
31642
- // ../templates/src/shared/index.ts
31643
- init_package_json();
31644
31754
  // ../templates/src/render.ts
31645
31755
  var import_ejs = __toESM(require_ejs(), 1);
31756
+ // ../templates/src/shared/index.ts
31757
+ init_package_json();
31646
31758
  // src/commands/add/component.ts
31647
31759
  init_dist();
31648
31760
  async function getPackageName(cwd2) {
@@ -33113,29 +33225,54 @@ async function enhancedInitCommand(options = {}) {
33113
33225
  options: [
33114
33226
  {
33115
33227
  value: "radix-maia",
33116
- label: "Maia (Recommended)",
33228
+ label: "Maia \u2014 Radix (Recommended)",
33117
33229
  hint: "Modern, clean design with soft shadows and refined aesthetics"
33118
33230
  },
33119
33231
  {
33120
33232
  value: "radix-vega",
33121
- label: "Vega",
33233
+ label: "Vega \u2014 Radix",
33122
33234
  hint: "Bold, vibrant design with stronger colors and contrast"
33123
33235
  },
33124
33236
  {
33125
33237
  value: "radix-nova",
33126
- label: "Nova",
33238
+ label: "Nova \u2014 Radix",
33127
33239
  hint: "Minimalist design with subtle accents and clean lines"
33128
33240
  },
33129
33241
  {
33130
33242
  value: "radix-lyra",
33131
- label: "Lyra",
33243
+ label: "Lyra \u2014 Radix",
33132
33244
  hint: "Elegant design with refined typography and spacing"
33133
33245
  },
33134
33246
  {
33135
33247
  value: "radix-mira",
33136
- label: "Mira",
33248
+ label: "Mira \u2014 Radix",
33137
33249
  hint: "Playful design with rounded elements and soft colors"
33138
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
+ },
33139
33276
  {
33140
33277
  value: "new-york",
33141
33278
  label: "New York (Legacy)",
@@ -33223,21 +33360,21 @@ async function enhancedInitCommand(options = {}) {
33223
33360
  }
33224
33361
  }
33225
33362
  let shadcnIconLibrary = getOptionValue("BUNKIT_SHADCN_ICON_LIBRARY", options.shadcnIconLibrary);
33226
- 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-");
33227
33364
  if (!shadcnIconLibrary && uiLibrary === "shadcn") {
33228
33365
  if (!isNonInteractive) {
33229
33366
  shadcnIconLibrary = await ve({
33230
33367
  message: "\uD83D\uDD23 Icon library",
33231
33368
  options: [
33232
33369
  {
33233
- value: "phosphor",
33234
- label: isModernShadcnStyle ? "Phosphor Icons (Recommended)" : "Phosphor Icons",
33235
- 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"
33236
33373
  },
33237
33374
  {
33238
- value: "iconoir",
33239
- label: !isModernShadcnStyle ? "Iconoir (Recommended)" : "Iconoir",
33240
- 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"
33241
33378
  },
33242
33379
  {
33243
33380
  value: "lucide",
@@ -33251,39 +33388,19 @@ async function enhancedInitCommand(options = {}) {
33251
33388
  process.exit(0);
33252
33389
  }
33253
33390
  } else {
33254
- shadcnIconLibrary = isModernShadcnStyle ? "phosphor" : "iconoir";
33391
+ shadcnIconLibrary = "iconoir";
33255
33392
  }
33256
33393
  }
33257
33394
  let shadcnBase = getOptionValue("BUNKIT_SHADCN_BASE", options.shadcnBase);
33258
- if (!shadcnBase && uiLibrary === "shadcn" && isModernShadcnStyle) {
33259
- if (!isNonInteractive) {
33260
- shadcnBase = await ve({
33261
- message: "\uD83E\uDDF1 Component foundation",
33262
- options: [
33263
- {
33264
- value: "radix",
33265
- label: "Radix UI (Recommended)",
33266
- hint: "Battle-tested accessibility primitives - industry standard"
33267
- },
33268
- {
33269
- value: "base-ui",
33270
- label: "Base UI",
33271
- hint: "Modern alternative with similar API - newer, experimental"
33272
- }
33273
- ]
33274
- });
33275
- if (pD(shadcnBase)) {
33276
- xe("Operation cancelled.");
33277
- process.exit(0);
33278
- }
33395
+ if (!shadcnBase && uiLibrary === "shadcn") {
33396
+ if (shadcnStyle?.startsWith("base-")) {
33397
+ shadcnBase = "base-ui";
33279
33398
  } else {
33280
33399
  shadcnBase = "radix";
33281
33400
  }
33282
- } else if (!shadcnBase) {
33283
- shadcnBase = "radix";
33284
33401
  }
33285
33402
  let shadcnMenuAccent = getOptionValue("BUNKIT_SHADCN_MENU_ACCENT", options.shadcnMenuAccent);
33286
- if (!shadcnMenuAccent && uiLibrary === "shadcn" && isModernShadcnStyle) {
33403
+ if (!shadcnMenuAccent && uiLibrary === "shadcn" && isModernStyle) {
33287
33404
  if (!isNonInteractive) {
33288
33405
  shadcnMenuAccent = await ve({
33289
33406
  message: "\u2728 Menu accent style",
@@ -33311,7 +33428,7 @@ async function enhancedInitCommand(options = {}) {
33311
33428
  shadcnMenuAccent = "subtle";
33312
33429
  }
33313
33430
  let shadcnMenuColor = getOptionValue("BUNKIT_SHADCN_MENU_COLOR", options.shadcnMenuColor);
33314
- if (!shadcnMenuColor && uiLibrary === "shadcn" && isModernShadcnStyle) {
33431
+ if (!shadcnMenuColor && uiLibrary === "shadcn" && isModernStyle) {
33315
33432
  if (!isNonInteractive) {
33316
33433
  shadcnMenuColor = await ve({
33317
33434
  message: "\uD83C\uDFAF Menu color scheme",
@@ -33338,6 +33455,21 @@ async function enhancedInitCommand(options = {}) {
33338
33455
  } else if (!shadcnMenuColor) {
33339
33456
  shadcnMenuColor = "default";
33340
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
+ }
33341
33473
  let testing = getOptionValue("BUNKIT_TESTING", options.testing, "bun-test");
33342
33474
  if (!testing) {
33343
33475
  if (!isNonInteractive) {
@@ -33584,6 +33716,7 @@ async function enhancedInitCommand(options = {}) {
33584
33716
  shadcnMenuAccent,
33585
33717
  shadcnMenuColor,
33586
33718
  shadcnRadius,
33719
+ shadcnRtl: shadcnRtl || false,
33587
33720
  supabasePreset,
33588
33721
  supabaseFeatures,
33589
33722
  supabaseWithDrizzle
@@ -33793,6 +33926,130 @@ async function enhancedInitCommand(options = {}) {
33793
33926
  }
33794
33927
  }
33795
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
+
33796
34053
  // src/commands/preset.ts
33797
34054
  init_src();
33798
34055
  init_boxen();
@@ -33906,12 +34163,13 @@ Examples:
33906
34163
  $ bunkit create nextjs my-app Create a Next.js web application
33907
34164
  $ bunkit add workspace Add a new workspace to monorepo
33908
34165
  $ bunkit add component --all Browse and add shadcn/ui components
34166
+ $ bunkit migrate radix Migrate to unified radix-ui package
33909
34167
  $ bunkit catalog add zod ^3.24.1 Add package to dependency catalog
33910
34168
  $ bunkit preset list List all custom presets
33911
34169
 
33912
34170
  For more information, visit: https://github.com/Arakiss/bunkit
33913
34171
  `);
33914
- 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", `
33915
34173
  Examples:
33916
34174
  $ bunkit init Interactive project creation
33917
34175
  $ bunkit init --name my-app --preset nextjs Quick web app creation
@@ -34031,6 +34289,28 @@ Features:
34031
34289
  process.exit(1);
34032
34290
  }
34033
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
+ });
34034
34314
  var catalogCmd = new Command("catalog").alias("cat").description("Manage dependency catalog for version synchronization").addHelpText("after", `
34035
34315
  Subcommands:
34036
34316
  add <package> [version] Add package to catalog