@mars-stack/cli 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ var init_logger = __esm({
45
45
 
46
46
  // src/utils/rollback.ts
47
47
  import fs3 from "fs-extra";
48
- import path3 from "path";
48
+ import path4 from "path";
49
49
  import os from "os";
50
50
  function createRollbackContext() {
51
51
  const manifest = {
@@ -53,14 +53,14 @@ function createRollbackContext() {
53
53
  filesModified: [],
54
54
  depsInstalled: []
55
55
  };
56
- const backupDir = path3.join(os.tmpdir(), `mars-rollback-${Date.now()}`);
56
+ const backupDir = path4.join(os.tmpdir(), `mars-rollback-${Date.now()}`);
57
57
  function trackCreatedFile(filePath) {
58
58
  manifest.filesCreated.push(filePath);
59
59
  }
60
60
  async function trackModifiedFile(filePath) {
61
61
  if (!await fs3.pathExists(filePath)) return;
62
62
  await fs3.ensureDir(backupDir);
63
- const backupPath = path3.join(backupDir, `${manifest.filesModified.length}-${path3.basename(filePath)}`);
63
+ const backupPath = path4.join(backupDir, `${manifest.filesModified.length}-${path4.basename(filePath)}`);
64
64
  await fs3.copy(filePath, backupPath);
65
65
  manifest.filesModified.push({ path: filePath, backup: backupPath });
66
66
  }
@@ -109,9 +109,9 @@ var init_rollback = __esm({
109
109
 
110
110
  // src/utils/dependencies.ts
111
111
  import fs4 from "fs-extra";
112
- import path4 from "path";
112
+ import path5 from "path";
113
113
  async function addDependencies(projectRoot, deps, dev = false) {
114
- const pkgPath = path4.join(projectRoot, "package.json");
114
+ const pkgPath = path5.join(projectRoot, "package.json");
115
115
  if (!await fs4.pathExists(pkgPath)) return;
116
116
  const pkg = await fs4.readJson(pkgPath);
117
117
  const key = dev ? "devDependencies" : "dependencies";
@@ -124,16 +124,40 @@ var init_dependencies = __esm({
124
124
  }
125
125
  });
126
126
 
127
+ // src/utils/routes.ts
128
+ import fs5 from "fs-extra";
129
+ import path6 from "path";
130
+ async function registerRoute(projectRoot, key, routePath, ctx) {
131
+ const routesFile = path6.join(projectRoot, "src", "config", "routes.ts");
132
+ if (!await fs5.pathExists(routesFile)) return;
133
+ let content = await fs5.readFile(routesFile, "utf-8");
134
+ if (content.includes(`${key}:`)) return;
135
+ if (ctx) {
136
+ await ctx.trackModifiedFile(routesFile);
137
+ }
138
+ content = content.replace(
139
+ /} as const;/,
140
+ ` ${key}: '${routePath}',
141
+ } as const;`
142
+ );
143
+ await fs5.writeFile(routesFile, content);
144
+ }
145
+ var init_routes = __esm({
146
+ "src/utils/routes.ts"() {
147
+ "use strict";
148
+ }
149
+ });
150
+
127
151
  // src/generators/features/blog.ts
128
152
  var blog_exports = {};
129
153
  __export(blog_exports, {
130
154
  generateBlog: () => generateBlog
131
155
  });
132
- import fs5 from "fs-extra";
133
- import path5 from "path";
156
+ import fs6 from "fs-extra";
157
+ import path7 from "path";
134
158
  async function generateBlog(projectRoot) {
135
- const featureDir = path5.join(projectRoot, "src", "features", "blog");
136
- if (await fs5.pathExists(featureDir)) {
159
+ const featureDir = path7.join(projectRoot, "src", "features", "blog");
160
+ if (await fs6.pathExists(featureDir)) {
137
161
  log.error("Blog feature already exists at src/features/blog/");
138
162
  return;
139
163
  }
@@ -154,10 +178,10 @@ async function generateBlog(projectRoot) {
154
178
  };
155
179
  let count = 0;
156
180
  for (const [filePath, content] of Object.entries(files)) {
157
- const fullPath = path5.join(projectRoot, filePath);
181
+ const fullPath = path7.join(projectRoot, filePath);
158
182
  ctx.trackCreatedFile(fullPath);
159
- await fs5.ensureDir(path5.dirname(fullPath));
160
- await fs5.writeFile(fullPath, content);
183
+ await fs6.ensureDir(path7.dirname(fullPath));
184
+ await fs6.writeFile(fullPath, content);
161
185
  count++;
162
186
  }
163
187
  await addDependencies(projectRoot, {
@@ -168,6 +192,7 @@ async function generateBlog(projectRoot) {
168
192
  "remark-gfm": "^4.0.0"
169
193
  });
170
194
  await addDependencies(projectRoot, { "@types/mdx": "^2.0.0" }, true);
195
+ await registerRoute(projectRoot, "blog", "/blog", ctx);
171
196
  await setConfigFlag(projectRoot, ctx);
172
197
  await ctx.commit();
173
198
  log.success(`Generated blog feature with ${count} files`);
@@ -183,12 +208,12 @@ async function generateBlog(projectRoot) {
183
208
  }
184
209
  }
185
210
  async function setConfigFlag(projectRoot, ctx) {
186
- const configPath = path5.join(projectRoot, "src", "config", "app.config.ts");
187
- if (!await fs5.pathExists(configPath)) return;
211
+ const configPath = path7.join(projectRoot, "src", "config", "app.config.ts");
212
+ if (!await fs6.pathExists(configPath)) return;
188
213
  await ctx.trackModifiedFile(configPath);
189
- const content = await fs5.readFile(configPath, "utf-8");
214
+ const content = await fs6.readFile(configPath, "utf-8");
190
215
  const updated = content.replace(/blog:\s*false/, "blog: true");
191
- await fs5.writeFile(configPath, updated);
216
+ await fs6.writeFile(configPath, updated);
192
217
  }
193
218
  function schemas() {
194
219
  return `${STAMP}
@@ -681,6 +706,7 @@ var init_blog = __esm({
681
706
  init_logger();
682
707
  init_rollback();
683
708
  init_dependencies();
709
+ init_routes();
684
710
  GENERATOR_VERSION = "0.1.0";
685
711
  STAMP = `// @mars-generated blog@${GENERATOR_VERSION}`;
686
712
  }
@@ -706,11 +732,11 @@ var dark_mode_exports = {};
706
732
  __export(dark_mode_exports, {
707
733
  generateDarkMode: () => generateDarkMode
708
734
  });
709
- import fs6 from "fs-extra";
710
- import path6 from "path";
735
+ import fs7 from "fs-extra";
736
+ import path8 from "path";
711
737
  async function generateDarkMode(projectRoot) {
712
- const featureDir = path6.join(projectRoot, "src", "features", "dark-mode");
713
- if (await fs6.pathExists(featureDir)) {
738
+ const featureDir = path8.join(projectRoot, "src", "features", "dark-mode");
739
+ if (await fs7.pathExists(featureDir)) {
714
740
  log.error("Dark mode feature already exists at src/features/dark-mode/");
715
741
  return;
716
742
  }
@@ -725,10 +751,10 @@ async function generateDarkMode(projectRoot) {
725
751
  };
726
752
  let count = 0;
727
753
  for (const [filePath, content] of Object.entries(files)) {
728
- const fullPath = path6.join(projectRoot, filePath);
754
+ const fullPath = path8.join(projectRoot, filePath);
729
755
  ctx.trackCreatedFile(fullPath);
730
- await fs6.ensureDir(path6.dirname(fullPath));
731
- await fs6.writeFile(fullPath, content);
756
+ await fs7.ensureDir(path8.dirname(fullPath));
757
+ await fs7.writeFile(fullPath, content);
732
758
  count++;
733
759
  }
734
760
  await wireLayout(projectRoot, ctx);
@@ -758,18 +784,18 @@ async function generateDarkMode(projectRoot) {
758
784
  }
759
785
  }
760
786
  async function setConfigFlag2(projectRoot, ctx) {
761
- const configPath = path6.join(projectRoot, "src", "config", "app.config.ts");
762
- if (!await fs6.pathExists(configPath)) return;
787
+ const configPath = path8.join(projectRoot, "src", "config", "app.config.ts");
788
+ if (!await fs7.pathExists(configPath)) return;
763
789
  await ctx.trackModifiedFile(configPath);
764
- const content = await fs6.readFile(configPath, "utf-8");
790
+ const content = await fs7.readFile(configPath, "utf-8");
765
791
  const updated = content.replace(/darkMode:\s*false/, "darkMode: true");
766
- await fs6.writeFile(configPath, updated);
792
+ await fs7.writeFile(configPath, updated);
767
793
  }
768
794
  async function wireLayout(projectRoot, ctx) {
769
- const layoutPath = path6.join(projectRoot, "src", "app", "layout.tsx");
770
- if (!await fs6.pathExists(layoutPath)) return;
795
+ const layoutPath = path8.join(projectRoot, "src", "app", "layout.tsx");
796
+ if (!await fs7.pathExists(layoutPath)) return;
771
797
  await ctx.trackModifiedFile(layoutPath);
772
- let content = await fs6.readFile(layoutPath, "utf-8");
798
+ let content = await fs7.readFile(layoutPath, "utf-8");
773
799
  if (!content.includes("getThemeScript")) {
774
800
  content = `import { getThemeScript } from '@/features/dark-mode';
775
801
  ${content}`;
@@ -783,13 +809,13 @@ ${content}`;
783
809
  "$1\n <head>\n <script dangerouslySetInnerHTML={{ __html: getThemeScript() }} />\n </head>\n$2"
784
810
  );
785
811
  }
786
- await fs6.writeFile(layoutPath, content);
812
+ await fs7.writeFile(layoutPath, content);
787
813
  }
788
814
  async function wireProviders(projectRoot, ctx) {
789
- const providersPath = path6.join(projectRoot, "src", "app", "providers.tsx");
790
- if (!await fs6.pathExists(providersPath)) return;
815
+ const providersPath = path8.join(projectRoot, "src", "app", "providers.tsx");
816
+ if (!await fs7.pathExists(providersPath)) return;
791
817
  await ctx.trackModifiedFile(providersPath);
792
- let content = await fs6.readFile(providersPath, "utf-8");
818
+ let content = await fs7.readFile(providersPath, "utf-8");
793
819
  if (content.includes("ThemeProvider")) return;
794
820
  content = insertImportAfterDirectives(
795
821
  content,
@@ -808,13 +834,13 @@ async function wireProviders(projectRoot, ctx) {
808
834
  );`;
809
835
  }
810
836
  );
811
- await fs6.writeFile(providersPath, content);
837
+ await fs7.writeFile(providersPath, content);
812
838
  }
813
839
  async function wireProtectedNav(projectRoot, ctx) {
814
- const navPath = path6.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
815
- if (!await fs6.pathExists(navPath)) return;
840
+ const navPath = path8.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
841
+ if (!await fs7.pathExists(navPath)) return;
816
842
  await ctx.trackModifiedFile(navPath);
817
- let content = await fs6.readFile(navPath, "utf-8");
843
+ let content = await fs7.readFile(navPath, "utf-8");
818
844
  if (content.includes("ThemeToggleSimple")) return;
819
845
  content = insertImportAfterDirectives(
820
846
  content,
@@ -829,19 +855,19 @@ async function wireProtectedNav(projectRoot, ctx) {
829
855
  /(<div className="border-t border-border-default px-4 py-3">\s*\n\s*<UserMenu \/>)/,
830
856
  '$1\n <div className="mt-2">\n <ThemeToggleSimple />\n </div>'
831
857
  );
832
- await fs6.writeFile(navPath, content);
858
+ await fs7.writeFile(navPath, content);
833
859
  }
834
860
  async function wireTailwindDarkMode(projectRoot, ctx) {
835
- const globalsPath = path6.join(projectRoot, "src", "styles", "globals.css");
836
- if (!await fs6.pathExists(globalsPath)) return;
861
+ const globalsPath = path8.join(projectRoot, "src", "styles", "globals.css");
862
+ if (!await fs7.pathExists(globalsPath)) return;
837
863
  await ctx.trackModifiedFile(globalsPath);
838
- let content = await fs6.readFile(globalsPath, "utf-8");
864
+ let content = await fs7.readFile(globalsPath, "utf-8");
839
865
  if (content.includes("@custom-variant dark")) return;
840
866
  content = content.replace(
841
867
  /@import 'tailwindcss';/,
842
868
  "@import 'tailwindcss';\n@custom-variant dark (&:where(.dark, .dark *));"
843
869
  );
844
- await fs6.writeFile(globalsPath, content);
870
+ await fs7.writeFile(globalsPath, content);
845
871
  }
846
872
  function themeProvider() {
847
873
  return `${STAMP2}
@@ -1121,19 +1147,19 @@ var init_dark_mode = __esm({
1121
1147
  });
1122
1148
 
1123
1149
  // src/utils/prisma.ts
1124
- import fs7 from "fs-extra";
1125
- import path7 from "path";
1150
+ import fs8 from "fs-extra";
1151
+ import path9 from "path";
1126
1152
  async function addUserRelation(projectRoot, field, ctx) {
1127
- const authPath = path7.join(projectRoot, "prisma", "schema", "auth.prisma");
1128
- if (!await fs7.pathExists(authPath)) return;
1153
+ const authPath = path9.join(projectRoot, "prisma", "schema", "auth.prisma");
1154
+ if (!await fs8.pathExists(authPath)) return;
1129
1155
  await ctx.trackModifiedFile(authPath);
1130
- let content = await fs7.readFile(authPath, "utf-8");
1156
+ let content = await fs8.readFile(authPath, "utf-8");
1131
1157
  if (content.includes(field)) return;
1132
1158
  const insertBefore = " @@index([email])";
1133
1159
  content = content.replace(insertBefore, ` ${field}
1134
1160
 
1135
1161
  ${insertBefore}`);
1136
- await fs7.writeFile(authPath, content);
1162
+ await fs8.writeFile(authPath, content);
1137
1163
  }
1138
1164
  var init_prisma = __esm({
1139
1165
  "src/utils/prisma.ts"() {
@@ -1146,11 +1172,11 @@ var notifications_exports = {};
1146
1172
  __export(notifications_exports, {
1147
1173
  generateNotifications: () => generateNotifications
1148
1174
  });
1149
- import fs8 from "fs-extra";
1150
- import path8 from "path";
1175
+ import fs9 from "fs-extra";
1176
+ import path10 from "path";
1151
1177
  async function generateNotifications(projectRoot) {
1152
- const featureDir = path8.join(projectRoot, "src", "features", "notifications");
1153
- if (await fs8.pathExists(featureDir)) {
1178
+ const featureDir = path10.join(projectRoot, "src", "features", "notifications");
1179
+ if (await fs9.pathExists(featureDir)) {
1154
1180
  log.error("Notifications feature already exists at src/features/notifications/");
1155
1181
  return;
1156
1182
  }
@@ -1173,10 +1199,10 @@ async function generateNotifications(projectRoot) {
1173
1199
  };
1174
1200
  let count = 0;
1175
1201
  for (const [filePath, content] of Object.entries(files)) {
1176
- const fullPath = path8.join(projectRoot, filePath);
1202
+ const fullPath = path10.join(projectRoot, filePath);
1177
1203
  ctx.trackCreatedFile(fullPath);
1178
- await fs8.ensureDir(path8.dirname(fullPath));
1179
- await fs8.writeFile(fullPath, content);
1204
+ await fs9.ensureDir(path10.dirname(fullPath));
1205
+ await fs9.writeFile(fullPath, content);
1180
1206
  count++;
1181
1207
  }
1182
1208
  await addUserRelation(projectRoot, "notifications Notification[]", ctx);
@@ -1203,10 +1229,10 @@ async function generateNotifications(projectRoot) {
1203
1229
  }
1204
1230
  }
1205
1231
  async function wireProtectedNav2(projectRoot, ctx) {
1206
- const navPath = path8.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
1207
- if (!await fs8.pathExists(navPath)) return;
1232
+ const navPath = path10.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
1233
+ if (!await fs9.pathExists(navPath)) return;
1208
1234
  await ctx.trackModifiedFile(navPath);
1209
- let content = await fs8.readFile(navPath, "utf-8");
1235
+ let content = await fs9.readFile(navPath, "utf-8");
1210
1236
  if (content.includes("NotificationBell")) return;
1211
1237
  content = insertImportAfterDirectives(
1212
1238
  content,
@@ -1217,15 +1243,15 @@ async function wireProtectedNav2(projectRoot, ctx) {
1217
1243
  /(<div className="flex items-center gap-3">)\s*\n(\s*)(<div className="hidden md:block">)/,
1218
1244
  "$1\n$2<NotificationBell />\n$2$3"
1219
1245
  );
1220
- await fs8.writeFile(navPath, content);
1246
+ await fs9.writeFile(navPath, content);
1221
1247
  }
1222
1248
  async function setConfigFlag3(projectRoot, ctx) {
1223
- const configPath = path8.join(projectRoot, "src", "config", "app.config.ts");
1224
- if (!await fs8.pathExists(configPath)) return;
1249
+ const configPath = path10.join(projectRoot, "src", "config", "app.config.ts");
1250
+ if (!await fs9.pathExists(configPath)) return;
1225
1251
  await ctx.trackModifiedFile(configPath);
1226
- const content = await fs8.readFile(configPath, "utf-8");
1252
+ const content = await fs9.readFile(configPath, "utf-8");
1227
1253
  const updated = content.replace(/notifications:\s*false/, "notifications: true");
1228
- await fs8.writeFile(configPath, updated);
1254
+ await fs9.writeFile(configPath, updated);
1229
1255
  }
1230
1256
  function prismaSchema() {
1231
1257
  return `// Generated by mars generate notifications (notifications@${GENERATOR_VERSION3})
@@ -1821,11 +1847,11 @@ var analytics_exports = {};
1821
1847
  __export(analytics_exports, {
1822
1848
  generateAnalytics: () => generateAnalytics
1823
1849
  });
1824
- import fs9 from "fs-extra";
1825
- import path9 from "path";
1850
+ import fs10 from "fs-extra";
1851
+ import path11 from "path";
1826
1852
  async function generateAnalytics(projectRoot) {
1827
- const featureDir = path9.join(projectRoot, "src", "features", "analytics");
1828
- if (await fs9.pathExists(featureDir)) {
1853
+ const featureDir = path11.join(projectRoot, "src", "features", "analytics");
1854
+ if (await fs10.pathExists(featureDir)) {
1829
1855
  log.error("Analytics feature already exists at src/features/analytics/");
1830
1856
  return;
1831
1857
  }
@@ -1840,10 +1866,10 @@ async function generateAnalytics(projectRoot) {
1840
1866
  };
1841
1867
  let count = 0;
1842
1868
  for (const [filePath, content] of Object.entries(files)) {
1843
- const fullPath = path9.join(projectRoot, filePath);
1869
+ const fullPath = path11.join(projectRoot, filePath);
1844
1870
  ctx.trackCreatedFile(fullPath);
1845
- await fs9.ensureDir(path9.dirname(fullPath));
1846
- await fs9.writeFile(fullPath, content);
1871
+ await fs10.ensureDir(path11.dirname(fullPath));
1872
+ await fs10.writeFile(fullPath, content);
1847
1873
  count++;
1848
1874
  }
1849
1875
  await wireProviders2(projectRoot, ctx);
@@ -1874,10 +1900,10 @@ async function generateAnalytics(projectRoot) {
1874
1900
  }
1875
1901
  }
1876
1902
  async function wireProviders2(projectRoot, ctx) {
1877
- const providersPath = path9.join(projectRoot, "src", "app", "providers.tsx");
1878
- if (!await fs9.pathExists(providersPath)) return;
1903
+ const providersPath = path11.join(projectRoot, "src", "app", "providers.tsx");
1904
+ if (!await fs10.pathExists(providersPath)) return;
1879
1905
  await ctx.trackModifiedFile(providersPath);
1880
- let content = await fs9.readFile(providersPath, "utf-8");
1906
+ let content = await fs10.readFile(providersPath, "utf-8");
1881
1907
  if (content.includes("AnalyticsProvider")) return;
1882
1908
  content = insertImportAfterDirectives(
1883
1909
  content,
@@ -1892,8 +1918,8 @@ async function wireProviders2(projectRoot, ctx) {
1892
1918
  );`;
1893
1919
  }
1894
1920
  );
1895
- await fs9.writeFile(providersPath, content);
1896
- const written = await fs9.readFile(providersPath, "utf-8");
1921
+ await fs10.writeFile(providersPath, content);
1922
+ const written = await fs10.readFile(providersPath, "utf-8");
1897
1923
  if (!written.includes("AnalyticsProvider")) {
1898
1924
  throw new Error(
1899
1925
  "wireProviders: AnalyticsProvider was not inserted into providers.tsx \u2014 the return statement pattern did not match. Review the template file structure."
@@ -1901,12 +1927,12 @@ async function wireProviders2(projectRoot, ctx) {
1901
1927
  }
1902
1928
  }
1903
1929
  async function setConfigFlag4(projectRoot, ctx) {
1904
- const configPath = path9.join(projectRoot, "src", "config", "app.config.ts");
1905
- if (!await fs9.pathExists(configPath)) return;
1930
+ const configPath = path11.join(projectRoot, "src", "config", "app.config.ts");
1931
+ if (!await fs10.pathExists(configPath)) return;
1906
1932
  await ctx.trackModifiedFile(configPath);
1907
- const content = await fs9.readFile(configPath, "utf-8");
1933
+ const content = await fs10.readFile(configPath, "utf-8");
1908
1934
  const updated = content.replace(/analytics:\s*false/, "analytics: true");
1909
- await fs9.writeFile(configPath, updated);
1935
+ await fs10.writeFile(configPath, updated);
1910
1936
  }
1911
1937
  function types3() {
1912
1938
  return `${STAMP4}
@@ -2265,11 +2291,11 @@ var command_palette_exports = {};
2265
2291
  __export(command_palette_exports, {
2266
2292
  generateCommandPalette: () => generateCommandPalette
2267
2293
  });
2268
- import fs10 from "fs-extra";
2269
- import path10 from "path";
2294
+ import fs11 from "fs-extra";
2295
+ import path12 from "path";
2270
2296
  async function generateCommandPalette(projectRoot) {
2271
- const featureDir = path10.join(projectRoot, "src", "features", "command-palette");
2272
- if (await fs10.pathExists(featureDir)) {
2297
+ const featureDir = path12.join(projectRoot, "src", "features", "command-palette");
2298
+ if (await fs11.pathExists(featureDir)) {
2273
2299
  log.error("Command palette feature already exists at src/features/command-palette/");
2274
2300
  return;
2275
2301
  }
@@ -2287,10 +2313,10 @@ async function generateCommandPalette(projectRoot) {
2287
2313
  };
2288
2314
  let count = 0;
2289
2315
  for (const [filePath, content] of Object.entries(files)) {
2290
- const fullPath = path10.join(projectRoot, filePath);
2316
+ const fullPath = path12.join(projectRoot, filePath);
2291
2317
  ctx.trackCreatedFile(fullPath);
2292
- await fs10.ensureDir(path10.dirname(fullPath));
2293
- await fs10.writeFile(fullPath, content);
2318
+ await fs11.ensureDir(path12.dirname(fullPath));
2319
+ await fs11.writeFile(fullPath, content);
2294
2320
  count++;
2295
2321
  }
2296
2322
  await addDependencies(projectRoot, { cmdk: "^1.0.0" });
@@ -2313,26 +2339,26 @@ async function generateCommandPalette(projectRoot) {
2313
2339
  }
2314
2340
  }
2315
2341
  async function wireLayout2(projectRoot, ctx) {
2316
- const layoutPath = path10.join(projectRoot, "src", "app", "layout.tsx");
2317
- if (!await fs10.pathExists(layoutPath)) return;
2318
- await ctx.trackModifiedFile(layoutPath);
2319
- let content = await fs10.readFile(layoutPath, "utf-8");
2342
+ const layoutPath = path12.join(projectRoot, "src", "app", "layout.tsx");
2343
+ if (!await fs11.pathExists(layoutPath)) return;
2344
+ let content = await fs11.readFile(layoutPath, "utf-8");
2320
2345
  if (content.includes("CommandPalette")) return;
2346
+ await ctx.trackModifiedFile(layoutPath);
2321
2347
  content = `import { CommandPalette } from '@/features/command-palette';
2322
2348
  ${content}`;
2323
2349
  content = content.replace(
2324
- /(\s*)(<Providers>\{children\}<\/Providers>)/,
2350
+ /(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
2325
2351
  "$1$2\n$1<CommandPalette />"
2326
2352
  );
2327
- await fs10.writeFile(layoutPath, content);
2353
+ await fs11.writeFile(layoutPath, content);
2328
2354
  }
2329
2355
  async function setConfigFlag5(projectRoot, ctx) {
2330
- const configPath = path10.join(projectRoot, "src", "config", "app.config.ts");
2331
- if (!await fs10.pathExists(configPath)) return;
2356
+ const configPath = path12.join(projectRoot, "src", "config", "app.config.ts");
2357
+ if (!await fs11.pathExists(configPath)) return;
2332
2358
  await ctx.trackModifiedFile(configPath);
2333
- const content = await fs10.readFile(configPath, "utf-8");
2359
+ const content = await fs11.readFile(configPath, "utf-8");
2334
2360
  const updated = content.replace(/commandPalette:\s*false/, "commandPalette: true");
2335
- await fs10.writeFile(configPath, updated);
2361
+ await fs11.writeFile(configPath, updated);
2336
2362
  }
2337
2363
  function types4() {
2338
2364
  return `${STAMP5}
@@ -2748,11 +2774,11 @@ var onboarding_exports = {};
2748
2774
  __export(onboarding_exports, {
2749
2775
  generateOnboarding: () => generateOnboarding
2750
2776
  });
2751
- import fs11 from "fs-extra";
2752
- import path11 from "path";
2777
+ import fs12 from "fs-extra";
2778
+ import path13 from "path";
2753
2779
  async function generateOnboarding(projectRoot) {
2754
- const featureDir = path11.join(projectRoot, "src", "features", "onboarding");
2755
- if (await fs11.pathExists(featureDir)) {
2780
+ const featureDir = path13.join(projectRoot, "src", "features", "onboarding");
2781
+ if (await fs12.pathExists(featureDir)) {
2756
2782
  log.error("Onboarding feature already exists at src/features/onboarding/");
2757
2783
  return;
2758
2784
  }
@@ -2774,13 +2800,14 @@ async function generateOnboarding(projectRoot) {
2774
2800
  };
2775
2801
  let count = 0;
2776
2802
  for (const [filePath, content] of Object.entries(files)) {
2777
- const fullPath = path11.join(projectRoot, filePath);
2803
+ const fullPath = path13.join(projectRoot, filePath);
2778
2804
  ctx.trackCreatedFile(fullPath);
2779
- await fs11.ensureDir(path11.dirname(fullPath));
2780
- await fs11.writeFile(fullPath, content);
2805
+ await fs12.ensureDir(path13.dirname(fullPath));
2806
+ await fs12.writeFile(fullPath, content);
2781
2807
  count++;
2782
2808
  }
2783
2809
  await addUserRelation(projectRoot, "onboardingProgress OnboardingProgress?", ctx);
2810
+ await registerRoute(projectRoot, "onboarding", "/onboarding", ctx);
2784
2811
  await setConfigFlag6(projectRoot, ctx);
2785
2812
  await ctx.commit();
2786
2813
  log.success(`Generated onboarding feature with ${count} files`);
@@ -2801,12 +2828,12 @@ async function generateOnboarding(projectRoot) {
2801
2828
  }
2802
2829
  }
2803
2830
  async function setConfigFlag6(projectRoot, ctx) {
2804
- const configPath = path11.join(projectRoot, "src", "config", "app.config.ts");
2805
- if (!await fs11.pathExists(configPath)) return;
2831
+ const configPath = path13.join(projectRoot, "src", "config", "app.config.ts");
2832
+ if (!await fs12.pathExists(configPath)) return;
2806
2833
  await ctx.trackModifiedFile(configPath);
2807
- const content = await fs11.readFile(configPath, "utf-8");
2834
+ const content = await fs12.readFile(configPath, "utf-8");
2808
2835
  const updated = content.replace(/onboarding:\s*false/, "onboarding: true");
2809
- await fs11.writeFile(configPath, updated);
2836
+ await fs12.writeFile(configPath, updated);
2810
2837
  }
2811
2838
  function prismaSchema2() {
2812
2839
  return `// Generated by mars generate onboarding (onboarding@${GENERATOR_VERSION6})
@@ -3322,6 +3349,7 @@ var init_onboarding = __esm({
3322
3349
  init_logger();
3323
3350
  init_rollback();
3324
3351
  init_prisma();
3352
+ init_routes();
3325
3353
  GENERATOR_VERSION6 = "0.1.0";
3326
3354
  STAMP6 = `// @mars-generated onboarding@${GENERATOR_VERSION6}`;
3327
3355
  }
@@ -3332,11 +3360,11 @@ var search_exports = {};
3332
3360
  __export(search_exports, {
3333
3361
  generateSearch: () => generateSearch
3334
3362
  });
3335
- import fs12 from "fs-extra";
3336
- import path12 from "path";
3363
+ import fs13 from "fs-extra";
3364
+ import path14 from "path";
3337
3365
  async function generateSearch(projectRoot) {
3338
- const featureDir = path12.join(projectRoot, "src", "features", "search");
3339
- if (await fs12.pathExists(featureDir)) {
3366
+ const featureDir = path14.join(projectRoot, "src", "features", "search");
3367
+ if (await fs13.pathExists(featureDir)) {
3340
3368
  log.error("Search feature already exists at src/features/search/");
3341
3369
  return;
3342
3370
  }
@@ -3349,25 +3377,30 @@ async function generateSearch(projectRoot) {
3349
3377
  "src/features/search/server/index.ts": serverIndex(),
3350
3378
  "src/features/search/hooks/use-search.ts": useSearchHook(),
3351
3379
  "src/features/search/components/SearchInput.tsx": searchInput(),
3380
+ "src/features/search/components/NavSearch.tsx": navSearch(),
3352
3381
  "src/features/search/components/index.ts": componentIndex5(),
3353
3382
  "src/features/search/index.ts": featureIndex4(),
3354
3383
  "src/app/api/protected/search/route.ts": searchRoute()
3355
3384
  };
3356
3385
  let count = 0;
3357
3386
  for (const [filePath, content] of Object.entries(files)) {
3358
- const fullPath = path12.join(projectRoot, filePath);
3387
+ const fullPath = path14.join(projectRoot, filePath);
3359
3388
  ctx.trackCreatedFile(fullPath);
3360
- await fs12.ensureDir(path12.dirname(fullPath));
3361
- await fs12.writeFile(fullPath, content);
3389
+ await fs13.ensureDir(path14.dirname(fullPath));
3390
+ await fs13.writeFile(fullPath, content);
3362
3391
  count++;
3363
3392
  }
3393
+ await wireProtectedNav3(projectRoot, ctx);
3364
3394
  await setConfigFlag7(projectRoot, ctx);
3365
3395
  await ctx.commit();
3366
- log.success(`Generated search feature with ${count} files`);
3396
+ log.success(`Generated and wired search feature (${count} files created)`);
3367
3397
  log.blank();
3368
3398
  log.step("src/features/search/ \u2014 types, validation, server logic, hooks, components");
3369
3399
  log.step("src/app/api/protected/search/ \u2014 authenticated search endpoint");
3370
3400
  log.blank();
3401
+ log.step("Wired automatically:");
3402
+ log.step(" \u2713 NavSearch added to navigation bar");
3403
+ log.blank();
3371
3404
  log.warn("Search uses Postgres full-text search by default (no extra setup).");
3372
3405
  log.blank();
3373
3406
  log.step("For Algolia: yarn add algoliasearch + set ALGOLIA_APP_ID, ALGOLIA_API_KEY env vars");
@@ -3381,13 +3414,30 @@ async function generateSearch(projectRoot) {
3381
3414
  throw error;
3382
3415
  }
3383
3416
  }
3417
+ async function wireProtectedNav3(projectRoot, ctx) {
3418
+ const navPath = path14.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
3419
+ if (!await fs13.pathExists(navPath)) return;
3420
+ let content = await fs13.readFile(navPath, "utf-8");
3421
+ if (content.includes("NavSearch")) return;
3422
+ await ctx.trackModifiedFile(navPath);
3423
+ content = insertImportAfterDirectives(
3424
+ content,
3425
+ `import { NavSearch } from '@/features/search';
3426
+ `
3427
+ );
3428
+ content = content.replace(
3429
+ /(<div className="flex items-center gap-3">)\s*\n(\s*)(<)/,
3430
+ "$1\n$2<NavSearch />\n$2$3"
3431
+ );
3432
+ await fs13.writeFile(navPath, content);
3433
+ }
3384
3434
  async function setConfigFlag7(projectRoot, ctx) {
3385
- const configPath = path12.join(projectRoot, "src", "config", "app.config.ts");
3386
- if (!await fs12.pathExists(configPath)) return;
3435
+ const configPath = path14.join(projectRoot, "src", "config", "app.config.ts");
3436
+ if (!await fs13.pathExists(configPath)) return;
3387
3437
  await ctx.trackModifiedFile(configPath);
3388
- const content = await fs12.readFile(configPath, "utf-8");
3438
+ const content = await fs13.readFile(configPath, "utf-8");
3389
3439
  const updated = content.replace(/search:\s*false/, "search: true");
3390
- await fs12.writeFile(configPath, updated);
3440
+ await fs13.writeFile(configPath, updated);
3391
3441
  }
3392
3442
  function types6() {
3393
3443
  return `${STAMP7}
@@ -3657,9 +3707,31 @@ export function SearchInput({
3657
3707
  }
3658
3708
  `;
3659
3709
  }
3710
+ function navSearch() {
3711
+ return `${STAMP7}
3712
+ 'use client';
3713
+
3714
+ import { useState } from 'react';
3715
+ import { SearchInput } from './SearchInput';
3716
+
3717
+ export function NavSearch() {
3718
+ const [query, setQuery] = useState('');
3719
+
3720
+ return (
3721
+ <SearchInput
3722
+ value={query}
3723
+ onQueryChange={setQuery}
3724
+ placeholder="Search\u2026"
3725
+ className="w-48 lg:w-64"
3726
+ />
3727
+ );
3728
+ }
3729
+ `;
3730
+ }
3660
3731
  function componentIndex5() {
3661
3732
  return `${STAMP7}
3662
3733
  export { SearchInput } from './SearchInput';
3734
+ export { NavSearch } from './NavSearch';
3663
3735
  `;
3664
3736
  }
3665
3737
  function featureIndex4() {
@@ -3667,7 +3739,7 @@ function featureIndex4() {
3667
3739
  export type { SearchResult, SearchOptions, SearchProvider } from './types';
3668
3740
  export { searchParamsSchema } from './validation/schemas';
3669
3741
  export type { SearchParams } from './validation/schemas';
3670
- export { SearchInput } from './components';
3742
+ export { SearchInput, NavSearch } from './components';
3671
3743
  export { useSearch } from './hooks/use-search';
3672
3744
  `;
3673
3745
  }
@@ -3706,6 +3778,7 @@ var GENERATOR_VERSION7, STAMP7;
3706
3778
  var init_search = __esm({
3707
3779
  "src/generators/features/search.ts"() {
3708
3780
  "use strict";
3781
+ init_client_file_patch();
3709
3782
  init_logger();
3710
3783
  init_rollback();
3711
3784
  GENERATOR_VERSION7 = "0.1.0";
@@ -3718,11 +3791,11 @@ var realtime_exports = {};
3718
3791
  __export(realtime_exports, {
3719
3792
  generateRealtime: () => generateRealtime
3720
3793
  });
3721
- import fs13 from "fs-extra";
3722
- import path13 from "path";
3794
+ import fs14 from "fs-extra";
3795
+ import path15 from "path";
3723
3796
  async function generateRealtime(projectRoot) {
3724
- const featureDir = path13.join(projectRoot, "src", "features", "realtime");
3725
- if (await fs13.pathExists(featureDir)) {
3797
+ const featureDir = path15.join(projectRoot, "src", "features", "realtime");
3798
+ if (await fs14.pathExists(featureDir)) {
3726
3799
  log.error("Realtime feature already exists at src/features/realtime/");
3727
3800
  return;
3728
3801
  }
@@ -3739,10 +3812,10 @@ async function generateRealtime(projectRoot) {
3739
3812
  };
3740
3813
  let count = 0;
3741
3814
  for (const [filePath, content] of Object.entries(files)) {
3742
- const fullPath = path13.join(projectRoot, filePath);
3815
+ const fullPath = path15.join(projectRoot, filePath);
3743
3816
  ctx.trackCreatedFile(fullPath);
3744
- await fs13.ensureDir(path13.dirname(fullPath));
3745
- await fs13.writeFile(fullPath, content);
3817
+ await fs14.ensureDir(path15.dirname(fullPath));
3818
+ await fs14.writeFile(fullPath, content);
3746
3819
  count++;
3747
3820
  }
3748
3821
  await setConfigFlag8(projectRoot, ctx);
@@ -3767,12 +3840,12 @@ async function generateRealtime(projectRoot) {
3767
3840
  }
3768
3841
  }
3769
3842
  async function setConfigFlag8(projectRoot, ctx) {
3770
- const configPath = path13.join(projectRoot, "src", "config", "app.config.ts");
3771
- if (!await fs13.pathExists(configPath)) return;
3843
+ const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
3844
+ if (!await fs14.pathExists(configPath)) return;
3772
3845
  await ctx.trackModifiedFile(configPath);
3773
- const content = await fs13.readFile(configPath, "utf-8");
3846
+ const content = await fs14.readFile(configPath, "utf-8");
3774
3847
  const updated = content.replace(/realtime:\s*false/, "realtime: true");
3775
- await fs13.writeFile(configPath, updated);
3848
+ await fs14.writeFile(configPath, updated);
3776
3849
  }
3777
3850
  function types7() {
3778
3851
  return `${STAMP8}
@@ -4081,11 +4154,11 @@ var ai_exports = {};
4081
4154
  __export(ai_exports, {
4082
4155
  generateAI: () => generateAI
4083
4156
  });
4084
- import fs14 from "fs-extra";
4085
- import path14 from "path";
4157
+ import fs15 from "fs-extra";
4158
+ import path16 from "path";
4086
4159
  async function generateAI(projectRoot) {
4087
- const featureDir = path14.join(projectRoot, "src", "features", "ai");
4088
- if (await fs14.pathExists(featureDir)) {
4160
+ const featureDir = path16.join(projectRoot, "src", "features", "ai");
4161
+ if (await fs15.pathExists(featureDir)) {
4089
4162
  log.error("AI feature already exists at src/features/ai/");
4090
4163
  return;
4091
4164
  }
@@ -4103,10 +4176,10 @@ async function generateAI(projectRoot) {
4103
4176
  };
4104
4177
  let count = 0;
4105
4178
  for (const [filePath, content] of Object.entries(files)) {
4106
- const fullPath = path14.join(projectRoot, filePath);
4179
+ const fullPath = path16.join(projectRoot, filePath);
4107
4180
  ctx.trackCreatedFile(fullPath);
4108
- await fs14.ensureDir(path14.dirname(fullPath));
4109
- await fs14.writeFile(fullPath, content);
4181
+ await fs15.ensureDir(path16.dirname(fullPath));
4182
+ await fs15.writeFile(fullPath, content);
4110
4183
  count++;
4111
4184
  }
4112
4185
  await addDependencies(projectRoot, {
@@ -4132,12 +4205,12 @@ async function generateAI(projectRoot) {
4132
4205
  }
4133
4206
  }
4134
4207
  async function setConfigFlag9(projectRoot, ctx) {
4135
- const configPath = path14.join(projectRoot, "src", "config", "app.config.ts");
4136
- if (!await fs14.pathExists(configPath)) return;
4208
+ const configPath = path16.join(projectRoot, "src", "config", "app.config.ts");
4209
+ if (!await fs15.pathExists(configPath)) return;
4137
4210
  await ctx.trackModifiedFile(configPath);
4138
- const content = await fs14.readFile(configPath, "utf-8");
4211
+ const content = await fs15.readFile(configPath, "utf-8");
4139
4212
  const updated = content.replace(/ai:\s*false/, "ai: true");
4140
- await fs14.writeFile(configPath, updated);
4213
+ await fs15.writeFile(configPath, updated);
4141
4214
  }
4142
4215
  function types8() {
4143
4216
  return `${STAMP9}
@@ -4576,11 +4649,11 @@ var cookie_consent_exports = {};
4576
4649
  __export(cookie_consent_exports, {
4577
4650
  generateCookieConsent: () => generateCookieConsent
4578
4651
  });
4579
- import fs15 from "fs-extra";
4580
- import path15 from "path";
4652
+ import fs16 from "fs-extra";
4653
+ import path17 from "path";
4581
4654
  async function generateCookieConsent(projectRoot) {
4582
- const featureDir = path15.join(projectRoot, "src", "features", "cookie-consent");
4583
- if (await fs15.pathExists(featureDir)) {
4655
+ const featureDir = path17.join(projectRoot, "src", "features", "cookie-consent");
4656
+ if (await fs16.pathExists(featureDir)) {
4584
4657
  log.error("Cookie consent feature already exists at src/features/cookie-consent/");
4585
4658
  return;
4586
4659
  }
@@ -4596,10 +4669,10 @@ async function generateCookieConsent(projectRoot) {
4596
4669
  };
4597
4670
  let count = 0;
4598
4671
  for (const [filePath, content] of Object.entries(files)) {
4599
- const fullPath = path15.join(projectRoot, filePath);
4672
+ const fullPath = path17.join(projectRoot, filePath);
4600
4673
  ctx.trackCreatedFile(fullPath);
4601
- await fs15.ensureDir(path15.dirname(fullPath));
4602
- await fs15.writeFile(fullPath, content);
4674
+ await fs16.ensureDir(path17.dirname(fullPath));
4675
+ await fs16.writeFile(fullPath, content);
4603
4676
  count++;
4604
4677
  }
4605
4678
  await wireLayout3(projectRoot, ctx);
@@ -4624,26 +4697,26 @@ async function generateCookieConsent(projectRoot) {
4624
4697
  }
4625
4698
  }
4626
4699
  async function wireLayout3(projectRoot, ctx) {
4627
- const layoutPath = path15.join(projectRoot, "src", "app", "layout.tsx");
4628
- if (!await fs15.pathExists(layoutPath)) return;
4629
- await ctx.trackModifiedFile(layoutPath);
4630
- let content = await fs15.readFile(layoutPath, "utf-8");
4700
+ const layoutPath = path17.join(projectRoot, "src", "app", "layout.tsx");
4701
+ if (!await fs16.pathExists(layoutPath)) return;
4702
+ let content = await fs16.readFile(layoutPath, "utf-8");
4631
4703
  if (content.includes("CookieConsentBanner")) return;
4704
+ await ctx.trackModifiedFile(layoutPath);
4632
4705
  content = `import { CookieConsentBanner } from '@/features/cookie-consent';
4633
4706
  ${content}`;
4634
4707
  content = content.replace(
4635
- /(\s*)(<Providers>\{children\}<\/Providers>)/,
4708
+ /(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
4636
4709
  "$1$2\n$1<CookieConsentBanner />"
4637
4710
  );
4638
- await fs15.writeFile(layoutPath, content);
4711
+ await fs16.writeFile(layoutPath, content);
4639
4712
  }
4640
4713
  async function setConfigFlag10(projectRoot, ctx) {
4641
- const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
4642
- if (!await fs15.pathExists(configPath)) return;
4714
+ const configPath = path17.join(projectRoot, "src", "config", "app.config.ts");
4715
+ if (!await fs16.pathExists(configPath)) return;
4643
4716
  await ctx.trackModifiedFile(configPath);
4644
- const content = await fs15.readFile(configPath, "utf-8");
4717
+ const content = await fs16.readFile(configPath, "utf-8");
4645
4718
  const updated = content.replace(/cookieConsent:\s*false/, "cookieConsent: true");
4646
- await fs15.writeFile(configPath, updated);
4719
+ await fs16.writeFile(configPath, updated);
4647
4720
  }
4648
4721
  function types9() {
4649
4722
  return `${STAMP10}
@@ -4928,11 +5001,11 @@ var coming_soon_exports = {};
4928
5001
  __export(coming_soon_exports, {
4929
5002
  generateComingSoon: () => generateComingSoon
4930
5003
  });
4931
- import fs16 from "fs-extra";
4932
- import path16 from "path";
5004
+ import fs17 from "fs-extra";
5005
+ import path18 from "path";
4933
5006
  async function generateComingSoon(projectRoot) {
4934
- const featureDir = path16.join(projectRoot, "src", "features", "coming-soon");
4935
- if (await fs16.pathExists(featureDir)) {
5007
+ const featureDir = path18.join(projectRoot, "src", "features", "coming-soon");
5008
+ if (await fs17.pathExists(featureDir)) {
4936
5009
  log.error("Coming soon feature already exists at src/features/coming-soon/");
4937
5010
  return;
4938
5011
  }
@@ -4949,12 +5022,13 @@ async function generateComingSoon(projectRoot) {
4949
5022
  };
4950
5023
  let count = 0;
4951
5024
  for (const [filePath, content] of Object.entries(files)) {
4952
- const fullPath = path16.join(projectRoot, filePath);
5025
+ const fullPath = path18.join(projectRoot, filePath);
4953
5026
  ctx.trackCreatedFile(fullPath);
4954
- await fs16.ensureDir(path16.dirname(fullPath));
4955
- await fs16.writeFile(fullPath, content);
5027
+ await fs17.ensureDir(path18.dirname(fullPath));
5028
+ await fs17.writeFile(fullPath, content);
4956
5029
  count++;
4957
5030
  }
5031
+ await registerRoute(projectRoot, "comingSoon", "/coming-soon", ctx);
4958
5032
  await setConfigFlag11(projectRoot, ctx);
4959
5033
  await ctx.commit();
4960
5034
  log.success(`Generated coming soon feature with ${count} files`);
@@ -4973,12 +5047,12 @@ async function generateComingSoon(projectRoot) {
4973
5047
  }
4974
5048
  }
4975
5049
  async function setConfigFlag11(projectRoot, ctx) {
4976
- const configPath = path16.join(projectRoot, "src", "config", "app.config.ts");
4977
- if (!await fs16.pathExists(configPath)) return;
5050
+ const configPath = path18.join(projectRoot, "src", "config", "app.config.ts");
5051
+ if (!await fs17.pathExists(configPath)) return;
4978
5052
  await ctx.trackModifiedFile(configPath);
4979
- const content = await fs16.readFile(configPath, "utf-8");
5053
+ const content = await fs17.readFile(configPath, "utf-8");
4980
5054
  const updated = content.replace(/comingSoon:\s*false/, "comingSoon: true");
4981
- await fs16.writeFile(configPath, updated);
5055
+ await fs17.writeFile(configPath, updated);
4982
5056
  }
4983
5057
  function types10() {
4984
5058
  return `${STAMP11}
@@ -5155,6 +5229,7 @@ var init_coming_soon = __esm({
5155
5229
  "use strict";
5156
5230
  init_logger();
5157
5231
  init_rollback();
5232
+ init_routes();
5158
5233
  GENERATOR_VERSION11 = "0.1.0";
5159
5234
  STAMP11 = `// @mars-generated coming-soon@${GENERATOR_VERSION11}`;
5160
5235
  }
@@ -5165,11 +5240,11 @@ var sentry_exports = {};
5165
5240
  __export(sentry_exports, {
5166
5241
  generateSentry: () => generateSentry
5167
5242
  });
5168
- import fs17 from "fs-extra";
5169
- import path17 from "path";
5243
+ import fs18 from "fs-extra";
5244
+ import path19 from "path";
5170
5245
  async function generateSentry(projectRoot) {
5171
- const featureDir = path17.join(projectRoot, "src", "features", "sentry");
5172
- if (await fs17.pathExists(featureDir)) {
5246
+ const featureDir = path19.join(projectRoot, "src", "features", "sentry");
5247
+ if (await fs18.pathExists(featureDir)) {
5173
5248
  log.error("Sentry feature already exists at src/features/sentry/");
5174
5249
  return;
5175
5250
  }
@@ -5185,24 +5260,27 @@ async function generateSentry(projectRoot) {
5185
5260
  };
5186
5261
  let count = 0;
5187
5262
  for (const [filePath, content] of Object.entries(files)) {
5188
- const fullPath = path17.join(projectRoot, filePath);
5263
+ const fullPath = path19.join(projectRoot, filePath);
5189
5264
  ctx.trackCreatedFile(fullPath);
5190
- await fs17.ensureDir(path17.dirname(fullPath));
5191
- await fs17.writeFile(fullPath, content);
5265
+ await fs18.ensureDir(path19.dirname(fullPath));
5266
+ await fs18.writeFile(fullPath, content);
5192
5267
  count++;
5193
5268
  }
5194
5269
  await addDependencies(projectRoot, { "@sentry/nextjs": "^8.0.0" });
5270
+ await wireRootLayout(projectRoot, ctx);
5195
5271
  await setConfigFlag12(projectRoot, ctx);
5196
5272
  await ctx.commit();
5197
- log.success(`Generated sentry feature with ${count} files`);
5273
+ log.success(`Generated and wired sentry feature (${count} files created)`);
5198
5274
  log.blank();
5199
5275
  log.step("src/features/sentry/ \u2014 types, client-init, server-init, ErrorBoundary");
5200
5276
  log.blank();
5277
+ log.step("Wired automatically:");
5278
+ log.step(" \u2713 ErrorBoundary wrapping app content in root layout");
5279
+ log.blank();
5201
5280
  log.warn("Optional: run npx @sentry/wizard@latest -i nextjs for full setup");
5202
5281
  log.blank();
5203
5282
  log.warn("Next steps:");
5204
5283
  log.step("Set NEXT_PUBLIC_SENTRY_DSN and SENTRY_DSN environment variables");
5205
- log.step("Wrap pages or layouts with <ErrorBoundary> for graceful error handling");
5206
5284
  log.step("Import client-init and server-init in your instrumentation files");
5207
5285
  log.blank();
5208
5286
  } catch (error) {
@@ -5210,13 +5288,27 @@ async function generateSentry(projectRoot) {
5210
5288
  throw error;
5211
5289
  }
5212
5290
  }
5291
+ async function wireRootLayout(projectRoot, ctx) {
5292
+ const layoutPath = path19.join(projectRoot, "src", "app", "layout.tsx");
5293
+ if (!await fs18.pathExists(layoutPath)) return;
5294
+ let content = await fs18.readFile(layoutPath, "utf-8");
5295
+ if (content.includes("ErrorBoundary")) return;
5296
+ await ctx.trackModifiedFile(layoutPath);
5297
+ content = `import { ErrorBoundary } from '@/features/sentry';
5298
+ ${content}`;
5299
+ content = content.replace(
5300
+ /(<Providers>)\{children\}(<\/Providers>)/,
5301
+ "$1<ErrorBoundary>{children}</ErrorBoundary>$2"
5302
+ );
5303
+ await fs18.writeFile(layoutPath, content);
5304
+ }
5213
5305
  async function setConfigFlag12(projectRoot, ctx) {
5214
- const configPath = path17.join(projectRoot, "src", "config", "app.config.ts");
5215
- if (!await fs17.pathExists(configPath)) return;
5306
+ const configPath = path19.join(projectRoot, "src", "config", "app.config.ts");
5307
+ if (!await fs18.pathExists(configPath)) return;
5216
5308
  await ctx.trackModifiedFile(configPath);
5217
- const content = await fs17.readFile(configPath, "utf-8");
5309
+ const content = await fs18.readFile(configPath, "utf-8");
5218
5310
  const updated = content.replace(/sentry:\s*false/, "sentry: true");
5219
- await fs17.writeFile(configPath, updated);
5311
+ await fs18.writeFile(configPath, updated);
5220
5312
  }
5221
5313
  function types11() {
5222
5314
  return `${STAMP12}
@@ -5361,11 +5453,11 @@ var feature_flags_exports = {};
5361
5453
  __export(feature_flags_exports, {
5362
5454
  generateFeatureFlags: () => generateFeatureFlags
5363
5455
  });
5364
- import fs18 from "fs-extra";
5365
- import path18 from "path";
5456
+ import fs19 from "fs-extra";
5457
+ import path20 from "path";
5366
5458
  async function generateFeatureFlags(projectRoot) {
5367
- const featureDir = path18.join(projectRoot, "src", "features", "feature-flags");
5368
- if (await fs18.pathExists(featureDir)) {
5459
+ const featureDir = path20.join(projectRoot, "src", "features", "feature-flags");
5460
+ if (await fs19.pathExists(featureDir)) {
5369
5461
  log.error("Feature flags feature already exists at src/features/feature-flags/");
5370
5462
  return;
5371
5463
  }
@@ -5384,10 +5476,10 @@ async function generateFeatureFlags(projectRoot) {
5384
5476
  };
5385
5477
  let count = 0;
5386
5478
  for (const [filePath, content] of Object.entries(files)) {
5387
- const fullPath = path18.join(projectRoot, filePath);
5479
+ const fullPath = path20.join(projectRoot, filePath);
5388
5480
  ctx.trackCreatedFile(fullPath);
5389
- await fs18.ensureDir(path18.dirname(fullPath));
5390
- await fs18.writeFile(fullPath, content);
5481
+ await fs19.ensureDir(path20.dirname(fullPath));
5482
+ await fs19.writeFile(fullPath, content);
5391
5483
  count++;
5392
5484
  }
5393
5485
  await setConfigFlag13(projectRoot, ctx);
@@ -5407,12 +5499,12 @@ async function generateFeatureFlags(projectRoot) {
5407
5499
  }
5408
5500
  }
5409
5501
  async function setConfigFlag13(projectRoot, ctx) {
5410
- const configPath = path18.join(projectRoot, "src", "config", "app.config.ts");
5411
- if (!await fs18.pathExists(configPath)) return;
5502
+ const configPath = path20.join(projectRoot, "src", "config", "app.config.ts");
5503
+ if (!await fs19.pathExists(configPath)) return;
5412
5504
  await ctx.trackModifiedFile(configPath);
5413
- const content = await fs18.readFile(configPath, "utf-8");
5505
+ const content = await fs19.readFile(configPath, "utf-8");
5414
5506
  const updated = content.replace(/featureFlags:\s*false/, "featureFlags: true");
5415
- await fs18.writeFile(configPath, updated);
5507
+ await fs19.writeFile(configPath, updated);
5416
5508
  }
5417
5509
  function types12() {
5418
5510
  return `${STAMP13}
@@ -5650,27 +5742,43 @@ var init_feature_flags = __esm({
5650
5742
  // src/index.ts
5651
5743
  import { Command } from "commander";
5652
5744
 
5745
+ // src/utils/version.ts
5746
+ import { createRequire } from "module";
5747
+ import path from "path";
5748
+ import { fileURLToPath } from "url";
5749
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
5750
+ function readVersionFromPackageJson(packageDir) {
5751
+ const require2 = createRequire(import.meta.url);
5752
+ const pkg = require2(path.join(packageDir, "package.json"));
5753
+ const v = pkg.version?.trim();
5754
+ return v ? v : "0.0.0";
5755
+ }
5756
+ function getCliVersion() {
5757
+ const packageRoot = path.resolve(__dirname, "..", "..");
5758
+ return readVersionFromPackageJson(packageRoot);
5759
+ }
5760
+
5653
5761
  // src/commands/create.ts
5654
5762
  init_logger();
5655
- import fs20 from "fs-extra";
5656
- import path20 from "path";
5763
+ import fs21 from "fs-extra";
5764
+ import path22 from "path";
5657
5765
  import os3 from "os";
5658
5766
  import ora from "ora";
5659
5767
  import pc2 from "picocolors";
5660
5768
  import prompts5 from "prompts";
5661
5769
 
5662
5770
  // src/utils/template.ts
5663
- import path from "path";
5664
- import { fileURLToPath } from "url";
5771
+ import path2 from "path";
5772
+ import { fileURLToPath as fileURLToPath2 } from "url";
5665
5773
  import fs from "fs-extra";
5666
- var __dirname = path.dirname(fileURLToPath(import.meta.url));
5774
+ var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
5667
5775
  function getTemplatePath() {
5668
5776
  const candidates = [
5669
5777
  // Built bundle: __dirname = packages/cli/dist/
5670
- path.resolve(__dirname, "..", "template"),
5671
- path.resolve(__dirname, "..", "..", "..", "template"),
5778
+ path2.resolve(__dirname2, "..", "template"),
5779
+ path2.resolve(__dirname2, "..", "..", "..", "template"),
5672
5780
  // Source (vitest): __dirname = packages/cli/src/utils/
5673
- path.resolve(__dirname, "..", "..", "..", "..", "template")
5781
+ path2.resolve(__dirname2, "..", "..", "..", "..", "template")
5674
5782
  ];
5675
5783
  for (const candidate of candidates) {
5676
5784
  if (fs.existsSync(candidate)) {
@@ -5682,7 +5790,7 @@ function getTemplatePath() {
5682
5790
  );
5683
5791
  }
5684
5792
  function resolveProjectPath(projectName) {
5685
- return path.resolve(process.cwd(), projectName);
5793
+ return path2.resolve(process.cwd(), projectName);
5686
5794
  }
5687
5795
 
5688
5796
  // src/prompts/project-info.ts
@@ -6032,7 +6140,6 @@ var DESIGN_DIRECTIONS = [
6032
6140
  function getDefaultTheme() {
6033
6141
  return {
6034
6142
  primaryColor: COLOR_PRESETS[0].value,
6035
- secondaryColor: "amber-400",
6036
6143
  font: FONT_CHOICES[0].value,
6037
6144
  designDirection: DESIGN_DIRECTIONS[0].value
6038
6145
  };
@@ -6073,7 +6180,6 @@ async function promptTheme() {
6073
6180
  if (response.primaryColor === void 0) return null;
6074
6181
  return {
6075
6182
  primaryColor: response.primaryColor,
6076
- secondaryColor: "amber-400",
6077
6183
  font: response.font,
6078
6184
  designDirection: response.designDirection
6079
6185
  };
@@ -6082,7 +6188,7 @@ async function promptTheme() {
6082
6188
  // src/generators/scaffold.ts
6083
6189
  init_logger();
6084
6190
  import fs2 from "fs-extra";
6085
- import path2 from "path";
6191
+ import path3 from "path";
6086
6192
  import { generateBrandCss } from "@mars-stack/ui/utils";
6087
6193
 
6088
6194
  // src/generators/app-config.ts
@@ -6125,15 +6231,7 @@ function generateAppConfig(config) {
6125
6231
  address: '',
6126
6232
  },
6127
6233
  theme: {
6128
- primaryColor: '${config.theme.primaryColor}' as string,
6129
- secondaryColor: '${config.theme.secondaryColor}' as string,
6130
6234
  font: '${config.theme.font}' as string,
6131
- designDirection: '${config.theme.designDirection}' as
6132
- | 'modern-saas'
6133
- | 'minimal'
6134
- | 'enterprise'
6135
- | 'creative'
6136
- | 'dashboard',
6137
6235
  },
6138
6236
  features: {
6139
6237
  ${featureEntries}
@@ -6276,8 +6374,8 @@ async function copyTemplateFiles(templateDir, targetDir) {
6276
6374
  async function walkAndCopy(src, dest) {
6277
6375
  const entries = await fs2.readdir(src, { withFileTypes: true });
6278
6376
  for (const entry of entries) {
6279
- const srcPath = path2.join(src, entry.name);
6280
- const destPath = path2.join(dest, entry.name);
6377
+ const srcPath = path3.join(src, entry.name);
6378
+ const destPath = path3.join(dest, entry.name);
6281
6379
  if (entry.isDirectory()) {
6282
6380
  if (IGNORED_DIRS.includes(entry.name)) continue;
6283
6381
  await fs2.ensureDir(destPath);
@@ -6295,8 +6393,8 @@ async function copyTemplateFiles(templateDir, targetDir) {
6295
6393
  function resolveMarsPackageRange(packageName, templateDir) {
6296
6394
  const shortName = packageName.replace("@mars-stack/", "");
6297
6395
  const candidates = [
6298
- path2.resolve(templateDir, "..", "packages", shortName, "package.json"),
6299
- path2.resolve(templateDir, "..", "..", "packages", shortName, "package.json")
6396
+ path3.resolve(templateDir, "..", "packages", shortName, "package.json"),
6397
+ path3.resolve(templateDir, "..", "..", "packages", shortName, "package.json")
6300
6398
  ];
6301
6399
  for (const candidate of candidates) {
6302
6400
  if (fs2.existsSync(candidate)) {
@@ -6307,7 +6405,7 @@ function resolveMarsPackageRange(packageName, templateDir) {
6307
6405
  }
6308
6406
  }
6309
6407
  }
6310
- const templatePkg = fs2.readJsonSync(path2.join(templateDir, "package.json"));
6408
+ const templatePkg = fs2.readJsonSync(path3.join(templateDir, "package.json"));
6311
6409
  const existing = templatePkg.dependencies?.[packageName];
6312
6410
  if (existing && existing !== "*") {
6313
6411
  if (/^[\^~>=]/.test(existing)) return existing;
@@ -6318,7 +6416,7 @@ function resolveMarsPackageRange(packageName, templateDir) {
6318
6416
  }
6319
6417
  function generatePackageJson(config) {
6320
6418
  const templateDir = getTemplatePath();
6321
- const templatePkg = path2.join(templateDir, "package.json");
6419
+ const templatePkg = path3.join(templateDir, "package.json");
6322
6420
  const pkg = fs2.readJsonSync(templatePkg);
6323
6421
  pkg.name = config.name;
6324
6422
  pkg.version = "0.1.0";
@@ -6518,7 +6616,7 @@ async function pruneDisabledFeatures(features, targetDir) {
6518
6616
  const flagValue = features[feature];
6519
6617
  if (flagValue !== false) continue;
6520
6618
  for (const relativePath of paths) {
6521
- const fullPath = path2.join(targetDir, relativePath);
6619
+ const fullPath = path3.join(targetDir, relativePath);
6522
6620
  if (await fs2.pathExists(fullPath)) {
6523
6621
  await fs2.remove(fullPath);
6524
6622
  log.step(`Pruned disabled feature path: ${relativePath}`);
@@ -6529,7 +6627,7 @@ async function pruneDisabledFeatures(features, targetDir) {
6529
6627
  }
6530
6628
  }
6531
6629
  if (relationsToRemove.length > 0) {
6532
- const authPath = path2.join(targetDir, "prisma", "schema", "auth.prisma");
6630
+ const authPath = path3.join(targetDir, "prisma", "schema", "auth.prisma");
6533
6631
  if (await fs2.pathExists(authPath)) {
6534
6632
  let content = await fs2.readFile(authPath, "utf-8");
6535
6633
  for (const field of relationsToRemove) {
@@ -6547,22 +6645,22 @@ async function scaffoldProject(targetDir, config) {
6547
6645
  await fs2.ensureDir(targetDir);
6548
6646
  const fileCount = await copyTemplateFiles(templateDir, targetDir);
6549
6647
  await pruneDisabledFeatures(config.features, targetDir);
6550
- await fs2.writeFile(path2.join(targetDir, "package.json"), generatePackageJson(config));
6648
+ await fs2.writeFile(path3.join(targetDir, "package.json"), generatePackageJson(config));
6551
6649
  await fs2.writeFile(
6552
- path2.join(targetDir, "src", "config", "app.config.ts"),
6650
+ path3.join(targetDir, "src", "config", "app.config.ts"),
6553
6651
  generateAppConfig(config)
6554
6652
  );
6555
- await fs2.writeFile(path2.join(targetDir, "docker-compose.yml"), generateDockerCompose(config));
6556
- await fs2.writeFile(path2.join(targetDir, "src", "app", "layout.tsx"), generateLayout(config));
6557
- await fs2.writeFile(path2.join(targetDir, ".env"), generateEnvFile(config));
6558
- await fs2.writeFile(path2.join(targetDir, ".env.example"), generateEnvExample(config));
6559
- await fs2.writeFile(path2.join(targetDir, ".gitignore"), generateGitignore());
6653
+ await fs2.writeFile(path3.join(targetDir, "docker-compose.yml"), generateDockerCompose(config));
6654
+ await fs2.writeFile(path3.join(targetDir, "src", "app", "layout.tsx"), generateLayout(config));
6655
+ await fs2.writeFile(path3.join(targetDir, ".env"), generateEnvFile(config));
6656
+ await fs2.writeFile(path3.join(targetDir, ".env.example"), generateEnvExample(config));
6657
+ await fs2.writeFile(path3.join(targetDir, ".gitignore"), generateGitignore());
6560
6658
  await patchGlobalsCssForScaffoldedProject(targetDir, config);
6561
6659
  await generateBrandCssForProject(targetDir, config);
6562
6660
  return { fileCount };
6563
6661
  }
6564
6662
  async function generateBrandCssForProject(targetDir, config) {
6565
- const brandPath = path2.join(targetDir, "src", "styles", "brand.css");
6663
+ const brandPath = path3.join(targetDir, "src", "styles", "brand.css");
6566
6664
  if (!await fs2.pathExists(brandPath)) return;
6567
6665
  const css = generateBrandCss(config.theme.primaryColor);
6568
6666
  await fs2.writeFile(brandPath, css);
@@ -6575,7 +6673,7 @@ var VALID_DESIGN_DIRECTIONS = [
6575
6673
  "dashboard"
6576
6674
  ];
6577
6675
  async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
6578
- const globalsPath = path2.join(targetDir, "src", "styles", "globals.css");
6676
+ const globalsPath = path3.join(targetDir, "src", "styles", "globals.css");
6579
6677
  if (!await fs2.pathExists(globalsPath)) return;
6580
6678
  let content = await fs2.readFile(globalsPath, "utf-8");
6581
6679
  content = content.replace(
@@ -6712,13 +6810,13 @@ async function generateSelectedFeatures(projectRoot, features) {
6712
6810
  }
6713
6811
 
6714
6812
  // src/utils/telemetry.ts
6715
- import fs19 from "fs-extra";
6716
- import path19 from "path";
6813
+ import fs20 from "fs-extra";
6814
+ import path21 from "path";
6717
6815
  import os2 from "os";
6718
- var RC_PATH = path19.join(os2.homedir(), ".marsrc");
6816
+ var RC_PATH = path21.join(os2.homedir(), ".marsrc");
6719
6817
  function isTelemetryEnabled() {
6720
6818
  try {
6721
- const config = fs19.readJsonSync(RC_PATH);
6819
+ const config = fs20.readJsonSync(RC_PATH);
6722
6820
  return config.enabled === true;
6723
6821
  } catch {
6724
6822
  return false;
@@ -6727,16 +6825,16 @@ function isTelemetryEnabled() {
6727
6825
  function enableTelemetry() {
6728
6826
  const config = loadOrCreateConfig();
6729
6827
  config.enabled = true;
6730
- fs19.writeJsonSync(RC_PATH, config, { spaces: 2 });
6828
+ fs20.writeJsonSync(RC_PATH, config, { spaces: 2 });
6731
6829
  }
6732
6830
  function disableTelemetry() {
6733
6831
  const config = loadOrCreateConfig();
6734
6832
  config.enabled = false;
6735
- fs19.writeJsonSync(RC_PATH, config, { spaces: 2 });
6833
+ fs20.writeJsonSync(RC_PATH, config, { spaces: 2 });
6736
6834
  }
6737
6835
  function loadOrCreateConfig() {
6738
6836
  try {
6739
- return fs19.readJsonSync(RC_PATH);
6837
+ return fs20.readJsonSync(RC_PATH);
6740
6838
  } catch {
6741
6839
  return { enabled: false, anonymousId: crypto.randomUUID() };
6742
6840
  }
@@ -6756,13 +6854,13 @@ function trackEvent(event, properties) {
6756
6854
  },
6757
6855
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6758
6856
  };
6759
- const logPath = path19.join(os2.homedir(), ".mars-telemetry.log");
6760
- fs19.appendFile(logPath, JSON.stringify(payload) + "\n").catch(() => {
6857
+ const logPath = path21.join(os2.homedir(), ".mars-telemetry.log");
6858
+ fs20.appendFile(logPath, JSON.stringify(payload) + "\n").catch(() => {
6761
6859
  });
6762
6860
  }
6763
6861
 
6764
6862
  // src/commands/create.ts
6765
- var RC_PATH2 = path20.join(os3.homedir(), ".marsrc");
6863
+ var RC_PATH2 = path22.join(os3.homedir(), ".marsrc");
6766
6864
  var PROJECT_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
6767
6865
  var MAX_PROJECT_NAME_LENGTH = 214;
6768
6866
  async function createCommand(projectName, options) {
@@ -6789,8 +6887,8 @@ async function createCommand(projectName, options) {
6789
6887
  const projectInfo = useDefaults ? getDefaultProjectInfo(projectName) : await promptProjectInfo(projectName);
6790
6888
  if (!projectInfo) return;
6791
6889
  const targetDir = resolveProjectPath(projectInfo.name);
6792
- if (await fs20.pathExists(targetDir)) {
6793
- const entries = await fs20.readdir(targetDir);
6890
+ if (await fs21.pathExists(targetDir)) {
6891
+ const entries = await fs21.readdir(targetDir);
6794
6892
  if (entries.length > 0) {
6795
6893
  log.error(`Directory "${projectInfo.name}" already exists and is not empty.`);
6796
6894
  return;
@@ -6831,7 +6929,7 @@ async function createCommand(projectName, options) {
6831
6929
  log.info("Scaffolding cancelled.");
6832
6930
  process.exit(0);
6833
6931
  }
6834
- if (!fs20.pathExistsSync(RC_PATH2)) {
6932
+ if (!fs21.pathExistsSync(RC_PATH2)) {
6835
6933
  const { telemetryOptIn } = await prompts5(
6836
6934
  {
6837
6935
  type: "confirm",
@@ -6870,10 +6968,10 @@ async function createCommand(projectName, options) {
6870
6968
  } catch (err) {
6871
6969
  spinner.fail("Failed to scaffold project");
6872
6970
  log.error(err instanceof Error ? err.message : String(err));
6873
- if (await fs20.pathExists(targetDir)) {
6971
+ if (await fs21.pathExists(targetDir)) {
6874
6972
  const cleanupSpinner = ora("Cleaning up...").start();
6875
6973
  try {
6876
- await fs20.remove(targetDir);
6974
+ await fs21.remove(targetDir);
6877
6975
  cleanupSpinner.succeed("Cleaned up partial scaffold");
6878
6976
  } catch {
6879
6977
  cleanupSpinner.warn(`Could not clean up ${targetDir}. You may need to remove it manually.`);
@@ -6888,8 +6986,8 @@ function countEnabled(flags) {
6888
6986
 
6889
6987
  // src/commands/doctor.ts
6890
6988
  init_logger();
6891
- import fs21 from "fs-extra";
6892
- import path21 from "path";
6989
+ import fs22 from "fs-extra";
6990
+ import path23 from "path";
6893
6991
  import { execSync } from "child_process";
6894
6992
  function commandExists(cmd) {
6895
6993
  try {
@@ -6909,12 +7007,12 @@ function getVersion(cmd) {
6909
7007
  async function checkForUpgrades() {
6910
7008
  log.blank();
6911
7009
  log.title("Upgrade Check");
6912
- const packageJsonPath = path21.join(process.cwd(), "package.json");
6913
- if (!fs21.pathExistsSync(packageJsonPath)) {
7010
+ const packageJsonPath = path23.join(process.cwd(), "package.json");
7011
+ if (!fs22.pathExistsSync(packageJsonPath)) {
6914
7012
  log.warn("No package.json found \u2014 skipping upgrade check.");
6915
7013
  return;
6916
7014
  }
6917
- const packageJson = fs21.readJsonSync(packageJsonPath);
7015
+ const packageJson = fs22.readJsonSync(packageJsonPath);
6918
7016
  const deps = packageJson.dependencies ?? {};
6919
7017
  const devDeps = packageJson.devDependencies ?? {};
6920
7018
  const currentRaw = deps["@mars-stack/core"] ?? devDeps["@mars-stack/core"];
@@ -6976,25 +7074,25 @@ async function doctorCommand(options) {
6976
7074
  },
6977
7075
  {
6978
7076
  name: "package.json exists",
6979
- check: () => fs21.pathExistsSync(path21.join(process.cwd(), "package.json"))
7077
+ check: () => fs22.pathExistsSync(path23.join(process.cwd(), "package.json"))
6980
7078
  },
6981
7079
  {
6982
7080
  name: ".env file exists",
6983
- check: () => fs21.pathExistsSync(path21.join(process.cwd(), ".env"))
7081
+ check: () => fs22.pathExistsSync(path23.join(process.cwd(), ".env"))
6984
7082
  },
6985
7083
  {
6986
7084
  name: "Prisma schema exists",
6987
- check: () => fs21.pathExistsSync(path21.join(process.cwd(), "prisma", "schema")) || fs21.pathExistsSync(path21.join(process.cwd(), "prisma", "schema.prisma"))
7085
+ check: () => fs22.pathExistsSync(path23.join(process.cwd(), "prisma", "schema")) || fs22.pathExistsSync(path23.join(process.cwd(), "prisma", "schema.prisma"))
6988
7086
  },
6989
7087
  {
6990
7088
  name: "node_modules installed",
6991
- check: () => fs21.pathExistsSync(path21.join(process.cwd(), "node_modules"))
7089
+ check: () => fs22.pathExistsSync(path23.join(process.cwd(), "node_modules"))
6992
7090
  },
6993
7091
  {
6994
7092
  name: "DATABASE_URL set",
6995
7093
  check: () => {
6996
7094
  try {
6997
- const envContent = fs21.readFileSync(path21.join(process.cwd(), ".env"), "utf-8");
7095
+ const envContent = fs22.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
6998
7096
  const match = envContent.match(/^DATABASE_URL=(.+)$/m);
6999
7097
  return match ? match[1] !== "" : false;
7000
7098
  } catch {
@@ -7006,7 +7104,7 @@ async function doctorCommand(options) {
7006
7104
  name: "JWT_SECRET set",
7007
7105
  check: () => {
7008
7106
  try {
7009
- const envContent = fs21.readFileSync(path21.join(process.cwd(), ".env"), "utf-8");
7107
+ const envContent = fs22.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
7010
7108
  const match = envContent.match(/^JWT_SECRET=(.+)$/m);
7011
7109
  if (!match || !match[1]) return false;
7012
7110
  return match[1].length >= 32 ? true : "set but too short (need >=32 chars)";
@@ -7051,16 +7149,16 @@ async function doctorCommand(options) {
7051
7149
 
7052
7150
  // src/commands/add.ts
7053
7151
  init_logger();
7054
- import fs23 from "fs-extra";
7055
- import path23 from "path";
7152
+ import fs24 from "fs-extra";
7153
+ import path25 from "path";
7056
7154
  import pc3 from "picocolors";
7057
7155
 
7058
7156
  // src/utils/doc-updater.ts
7059
- import fs22 from "fs-extra";
7060
- import path22 from "path";
7157
+ import fs23 from "fs-extra";
7158
+ import path24 from "path";
7061
7159
  async function appendToDbSchema(projectDir, modelName, fields) {
7062
- const filePath = path22.join(projectDir, "docs", "generated", "db-schema.md");
7063
- if (!await fs22.pathExists(filePath)) return;
7160
+ const filePath = path24.join(projectDir, "docs", "generated", "db-schema.md");
7161
+ if (!await fs23.pathExists(filePath)) return;
7064
7162
  const entry = [
7065
7163
  "",
7066
7164
  `### ${modelName}`,
@@ -7070,12 +7168,12 @@ async function appendToDbSchema(projectDir, modelName, fields) {
7070
7168
  ...fields.map((f) => `| ${f} | |`),
7071
7169
  ""
7072
7170
  ].join("\n");
7073
- await fs22.appendFile(filePath, entry);
7171
+ await fs23.appendFile(filePath, entry);
7074
7172
  }
7075
7173
  async function updateQualityScore(projectDir, domain, grade) {
7076
- const filePath = path22.join(projectDir, "docs", "QUALITY_SCORE.md");
7077
- if (!await fs22.pathExists(filePath)) return;
7078
- const content = await fs22.readFile(filePath, "utf-8");
7174
+ const filePath = path24.join(projectDir, "docs", "QUALITY_SCORE.md");
7175
+ if (!await fs23.pathExists(filePath)) return;
7176
+ const content = await fs23.readFile(filePath, "utf-8");
7079
7177
  const pattern = new RegExp(`^\\|\\s*${escapeRegex(domain)}\\s*\\|`, "m");
7080
7178
  if (pattern.test(content)) {
7081
7179
  const updated = content.replace(pattern, (match) => {
@@ -7083,11 +7181,11 @@ async function updateQualityScore(projectDir, domain, grade) {
7083
7181
  parts[2] = ` ${grade} `;
7084
7182
  return parts.join("|");
7085
7183
  });
7086
- await fs22.writeFile(filePath, updated);
7184
+ await fs23.writeFile(filePath, updated);
7087
7185
  } else {
7088
7186
  const row = `| ${domain} | ${grade} | |
7089
7187
  `;
7090
- await fs22.appendFile(filePath, row);
7188
+ await fs23.appendFile(filePath, row);
7091
7189
  }
7092
7190
  }
7093
7191
  function escapeRegex(str) {
@@ -7098,8 +7196,8 @@ function escapeRegex(str) {
7098
7196
  init_rollback();
7099
7197
  function ensureInProject() {
7100
7198
  const cwd = process.cwd();
7101
- const configPath = path23.join(cwd, "src", "config", "app.config.ts");
7102
- if (!fs23.pathExistsSync(configPath)) {
7199
+ const configPath = path25.join(cwd, "src", "config", "app.config.ts");
7200
+ if (!fs24.pathExistsSync(configPath)) {
7103
7201
  log.error("Not inside a MARS project. Run this command from the project root.");
7104
7202
  process.exit(1);
7105
7203
  }
@@ -7132,8 +7230,8 @@ async function addFeatureCommand(name) {
7132
7230
  const kebab = toKebab(name);
7133
7231
  const pascal = toPascal(name);
7134
7232
  const camel = toCamel(name);
7135
- const featureDir = path23.join(root, "src", "features", kebab);
7136
- if (await fs23.pathExists(featureDir)) {
7233
+ const featureDir = path25.join(root, "src", "features", kebab);
7234
+ if (await fs24.pathExists(featureDir)) {
7137
7235
  log.error(`Feature "${kebab}" already exists at src/features/${kebab}/`);
7138
7236
  return;
7139
7237
  }
@@ -7198,9 +7296,9 @@ export type Update${pascal}Input = z.infer<typeof ${camel}Schemas.update>;
7198
7296
  ctx.trackCreatedFile(featureDir);
7199
7297
  let count = 0;
7200
7298
  for (const [filePath, content] of Object.entries(files)) {
7201
- const fullPath = path23.join(featureDir, filePath);
7202
- await fs23.ensureDir(path23.dirname(fullPath));
7203
- await fs23.writeFile(fullPath, content);
7299
+ const fullPath = path25.join(featureDir, filePath);
7300
+ await fs24.ensureDir(path25.dirname(fullPath));
7301
+ await fs24.writeFile(fullPath, content);
7204
7302
  count++;
7205
7303
  }
7206
7304
  await ctx.commit();
@@ -7221,8 +7319,8 @@ async function addPageCommand(routePath, options) {
7221
7319
  const root = ensureInProject();
7222
7320
  const cleanPath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
7223
7321
  const group = options.protected ? "(protected)" : "(public)";
7224
- const pageDir = path23.join(root, "src", "app", group, cleanPath);
7225
- if (await fs23.pathExists(path23.join(pageDir, "page.tsx"))) {
7322
+ const pageDir = path25.join(root, "src", "app", group, cleanPath);
7323
+ if (await fs24.pathExists(path25.join(pageDir, "page.tsx"))) {
7226
7324
  log.error(`Page already exists at src/app/${group}/${cleanPath}/page.tsx`);
7227
7325
  return;
7228
7326
  }
@@ -7270,9 +7368,9 @@ export default function Loading() {
7270
7368
  const ctx = createRollbackContext();
7271
7369
  try {
7272
7370
  ctx.trackCreatedFile(pageDir);
7273
- await fs23.ensureDir(pageDir);
7274
- await fs23.writeFile(path23.join(pageDir, "page.tsx"), pageContent);
7275
- await fs23.writeFile(path23.join(pageDir, "loading.tsx"), loadingContent);
7371
+ await fs24.ensureDir(pageDir);
7372
+ await fs24.writeFile(path25.join(pageDir, "page.tsx"), pageContent);
7373
+ await fs24.writeFile(path25.join(pageDir, "loading.tsx"), loadingContent);
7276
7374
  await ctx.commit();
7277
7375
  log.success(`Created page at ${pc3.bold(`src/app/${group}/${cleanPath}/`)}`);
7278
7376
  trackEvent("add", { type: "page" });
@@ -7289,9 +7387,9 @@ async function addModelCommand(name) {
7289
7387
  const pascal = toPascal(name);
7290
7388
  const camel = toCamel(name);
7291
7389
  const kebab = toKebab(name);
7292
- const schemaDir = path23.join(root, "prisma", "schema");
7293
- const schemaFile = path23.join(schemaDir, `${kebab}.prisma`);
7294
- if (await fs23.pathExists(schemaFile)) {
7390
+ const schemaDir = path25.join(root, "prisma", "schema");
7391
+ const schemaFile = path25.join(schemaDir, `${kebab}.prisma`);
7392
+ if (await fs24.pathExists(schemaFile)) {
7295
7393
  log.error(`Schema file already exists: prisma/schema/${kebab}.prisma`);
7296
7394
  return;
7297
7395
  }
@@ -7309,8 +7407,8 @@ async function addModelCommand(name) {
7309
7407
  const ctx = createRollbackContext();
7310
7408
  try {
7311
7409
  ctx.trackCreatedFile(schemaFile);
7312
- await fs23.ensureDir(schemaDir);
7313
- await fs23.writeFile(schemaFile, content);
7410
+ await fs24.ensureDir(schemaDir);
7411
+ await fs24.writeFile(schemaFile, content);
7314
7412
  await ctx.commit();
7315
7413
  log.success(`Created model ${pc3.bold(pascal)} at prisma/schema/${kebab}.prisma`);
7316
7414
  trackEvent("add", { type: "model" });
@@ -7332,9 +7430,9 @@ async function addEmailCommand(name) {
7332
7430
  const kebab = toKebab(name);
7333
7431
  const pascal = toPascal(name);
7334
7432
  const camel = toCamel(name);
7335
- const templatesDir = path23.join(root, "src", "lib", "core", "email", "templates");
7336
- const filePath = path23.join(templatesDir, `${kebab}-email.ts`);
7337
- if (await fs23.pathExists(filePath)) {
7433
+ const templatesDir = path25.join(root, "src", "lib", "core", "email", "templates");
7434
+ const filePath = path25.join(templatesDir, `${kebab}-email.ts`);
7435
+ if (await fs24.pathExists(filePath)) {
7338
7436
  log.error(`Email template already exists: src/lib/core/email/templates/${kebab}-email.ts`);
7339
7437
  return;
7340
7438
  }
@@ -7378,25 +7476,25 @@ export function ${functionName}({ appName, actionUrl, userName }: ${pascal}Email
7378
7476
  }
7379
7477
  `;
7380
7478
  const ctx = createRollbackContext();
7381
- const indexPath = path23.join(templatesDir, "index.ts");
7479
+ const indexPath = path25.join(templatesDir, "index.ts");
7382
7480
  try {
7383
7481
  ctx.trackCreatedFile(filePath);
7384
- if (await fs23.pathExists(indexPath)) {
7482
+ if (await fs24.pathExists(indexPath)) {
7385
7483
  await ctx.trackModifiedFile(indexPath);
7386
7484
  } else {
7387
7485
  ctx.trackCreatedFile(indexPath);
7388
7486
  }
7389
- await fs23.ensureDir(templatesDir);
7390
- await fs23.writeFile(filePath, content);
7487
+ await fs24.ensureDir(templatesDir);
7488
+ await fs24.writeFile(filePath, content);
7391
7489
  const exportLine = `export { ${functionName} } from './${kebab}-email';
7392
7490
  `;
7393
- if (await fs23.pathExists(indexPath)) {
7394
- const existing = await fs23.readFile(indexPath, "utf-8");
7491
+ if (await fs24.pathExists(indexPath)) {
7492
+ const existing = await fs24.readFile(indexPath, "utf-8");
7395
7493
  if (!existing.includes(functionName)) {
7396
- await fs23.appendFile(indexPath, exportLine);
7494
+ await fs24.appendFile(indexPath, exportLine);
7397
7495
  }
7398
7496
  } else {
7399
- await fs23.writeFile(indexPath, exportLine);
7497
+ await fs24.writeFile(indexPath, exportLine);
7400
7498
  }
7401
7499
  await ctx.commit();
7402
7500
  log.success(`Created email template ${pc3.bold(kebab)} at src/lib/core/email/templates/${kebab}-email.ts`);
@@ -7419,10 +7517,10 @@ async function addComponentCommand(name, options) {
7419
7517
  log.error(`Invalid type "${type}". Use: ${validTypes.join(", ")}`);
7420
7518
  return;
7421
7519
  }
7422
- const dir = type === "primitive" ? path23.join(root, "src", "components", "primitives") : path23.join(root, "src", "components", "patterns");
7423
- await fs23.ensureDir(dir);
7424
- const filePath = path23.join(dir, `${pascal}.tsx`);
7425
- if (await fs23.pathExists(filePath)) {
7520
+ const dir = type === "primitive" ? path25.join(root, "src", "components", "primitives") : path25.join(root, "src", "components", "patterns");
7521
+ await fs24.ensureDir(dir);
7522
+ const filePath = path25.join(dir, `${pascal}.tsx`);
7523
+ if (await fs24.pathExists(filePath)) {
7426
7524
  log.error(`Component already exists: ${pascal}.tsx`);
7427
7525
  return;
7428
7526
  }
@@ -7471,9 +7569,9 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
7471
7569
  const ctx = createRollbackContext();
7472
7570
  try {
7473
7571
  ctx.trackCreatedFile(filePath);
7474
- await fs23.writeFile(filePath, content);
7572
+ await fs24.writeFile(filePath, content);
7475
7573
  await ctx.commit();
7476
- log.success(`Created ${type} component ${pc3.bold(pascal)} at ${path23.relative(root, filePath)}`);
7574
+ log.success(`Created ${type} component ${pc3.bold(pascal)} at ${path25.relative(root, filePath)}`);
7477
7575
  trackEvent("add", { type: "component", componentType: type });
7478
7576
  log.blank();
7479
7577
  log.warn(`Remember to add the export to the barrel file:`);
@@ -7488,14 +7586,14 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
7488
7586
  // src/commands/configure.ts
7489
7587
  init_logger();
7490
7588
  import { execSync as execSync2 } from "child_process";
7491
- import fs24 from "fs-extra";
7492
- import path24 from "path";
7589
+ import fs25 from "fs-extra";
7590
+ import path26 from "path";
7493
7591
  import pc4 from "picocolors";
7494
7592
  import prompts6 from "prompts";
7495
7593
  function ensureInProject2() {
7496
7594
  const cwd = process.cwd();
7497
- const configPath = path24.join(cwd, "src", "config", "app.config.ts");
7498
- if (!fs24.pathExistsSync(configPath)) {
7595
+ const configPath = path26.join(cwd, "src", "config", "app.config.ts");
7596
+ if (!fs25.pathExistsSync(configPath)) {
7499
7597
  log.error("Not inside a MARS project. Run this command from the project root.");
7500
7598
  process.exit(1);
7501
7599
  }
@@ -7509,8 +7607,8 @@ var PROVIDER_DEPENDENCIES = {
7509
7607
  "storage:s3": ["@aws-sdk/client-s3", "@aws-sdk/s3-request-presigner"]
7510
7608
  };
7511
7609
  function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
7512
- const configPath = path24.join(projectDir, "src", "config", "app.config.ts");
7513
- let content = fs24.readFileSync(configPath, "utf-8");
7610
+ const configPath = path26.join(projectDir, "src", "config", "app.config.ts");
7611
+ let content = fs25.readFileSync(configPath, "utf-8");
7514
7612
  if (serviceKey === "auth") {
7515
7613
  const boolValue = provider === "google" ? "true" : "false";
7516
7614
  const featureRegex = new RegExp(`(googleOAuth\\s*:\\s*)(?:true|false)`);
@@ -7525,10 +7623,10 @@ function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
7525
7623
  content = content.replace(featureRegex, `$1true`);
7526
7624
  }
7527
7625
  }
7528
- fs24.writeFileSync(configPath, content);
7626
+ fs25.writeFileSync(configPath, content);
7529
7627
  }
7530
7628
  function detectPackageManager(projectDir) {
7531
- if (fs24.existsSync(path24.join(projectDir, "yarn.lock"))) return "yarn";
7629
+ if (fs25.existsSync(path26.join(projectDir, "yarn.lock"))) return "yarn";
7532
7630
  return "npm";
7533
7631
  }
7534
7632
  function installDependencies(projectDir, deps) {
@@ -7638,15 +7736,15 @@ async function configureCommand(service) {
7638
7736
  log.step(`Manually set ${pc4.bold(`services.${serviceConfig.configKey}.provider`)} to ${pc4.bold(`'${provider}'`)} in ${pc4.dim("src/config/app.config.ts")}`);
7639
7737
  }
7640
7738
  if (selectedProvider.envVars.length > 0) {
7641
- const envPath = path24.join(root, ".env");
7642
- if (await fs24.pathExists(envPath)) {
7643
- const envContent = await fs24.readFile(envPath, "utf-8");
7739
+ const envPath = path26.join(root, ".env");
7740
+ if (await fs25.pathExists(envPath)) {
7741
+ const envContent = await fs25.readFile(envPath, "utf-8");
7644
7742
  const missingVars = selectedProvider.envVars.filter(
7645
7743
  (v) => !envContent.includes(v)
7646
7744
  );
7647
7745
  if (missingVars.length > 0) {
7648
7746
  const additions = missingVars.map((v) => `${v}=""`).join("\n");
7649
- await fs24.appendFile(envPath, `
7747
+ await fs25.appendFile(envPath, `
7650
7748
  # ${selectedService} (${provider})
7651
7749
  ${additions}
7652
7750
  `);
@@ -7675,15 +7773,15 @@ ${additions}
7675
7773
 
7676
7774
  // src/commands/deploy.ts
7677
7775
  init_logger();
7678
- import fs25 from "fs-extra";
7679
- import path25 from "path";
7776
+ import fs26 from "fs-extra";
7777
+ import path27 from "path";
7680
7778
  import { execSync as execSync3 } from "child_process";
7681
7779
  import pc5 from "picocolors";
7682
7780
  import prompts7 from "prompts";
7683
7781
  function ensureInProject3() {
7684
7782
  const cwd = process.cwd();
7685
- const configPath = path25.join(cwd, "src", "config", "app.config.ts");
7686
- if (!fs25.pathExistsSync(configPath)) {
7783
+ const configPath = path27.join(cwd, "src", "config", "app.config.ts");
7784
+ if (!fs26.pathExistsSync(configPath)) {
7687
7785
  log.error("Not inside a MARS project. Run this command from the project root.");
7688
7786
  process.exit(1);
7689
7787
  }
@@ -7722,8 +7820,8 @@ async function deployCommand() {
7722
7820
  return;
7723
7821
  }
7724
7822
  }
7725
- const vercelDir = path25.join(root, ".vercel");
7726
- if (!fs25.existsSync(vercelDir)) {
7823
+ const vercelDir = path27.join(root, ".vercel");
7824
+ if (!fs26.existsSync(vercelDir)) {
7727
7825
  log.step("Linking project to Vercel...");
7728
7826
  try {
7729
7827
  execSync3("vercel link", { cwd: root, stdio: "inherit" });
@@ -7740,8 +7838,8 @@ async function deployCommand() {
7740
7838
  log.blank();
7741
7839
  const requiredVars = ["JWT_SECRET", "DATABASE_URL"];
7742
7840
  log.step(`Core: ${pc5.dim(requiredVars.join(", "))}`);
7743
- const configPath = path25.join(root, "src", "config", "app.config.ts");
7744
- const configContent = await fs25.readFile(configPath, "utf-8");
7841
+ const configPath = path27.join(root, "src", "config", "app.config.ts");
7842
+ const configContent = await fs26.readFile(configPath, "utf-8");
7745
7843
  if (configContent.includes("email: { provider: 'sendgrid'")) {
7746
7844
  log.step(`Email (SendGrid): ${pc5.dim("SENDGRID_API_KEY, SENDGRID_FROM_EMAIL")}`);
7747
7845
  } else if (configContent.includes("email: { provider: 'resend'")) {
@@ -7802,8 +7900,8 @@ async function deployCommand() {
7802
7900
 
7803
7901
  // src/commands/upgrade.ts
7804
7902
  init_logger();
7805
- import fs26 from "fs-extra";
7806
- import path26 from "path";
7903
+ import fs27 from "fs-extra";
7904
+ import path28 from "path";
7807
7905
  import { execSync as execSync4 } from "child_process";
7808
7906
  var MARS_PACKAGES = ["@mars-stack/core", "@mars-stack/ui"];
7809
7907
  var CHANGELOG_URL = "https://github.com/greaveselliott/mars/blob/main/CHANGELOG.md";
@@ -7826,7 +7924,7 @@ function readCurrentVersion(packageJson, packageName) {
7826
7924
  return version ? version.replace(/^[\^~]/, "") : null;
7827
7925
  }
7828
7926
  function detectPackageManager2(projectDir) {
7829
- if (fs26.pathExistsSync(path26.join(projectDir, "yarn.lock"))) {
7927
+ if (fs27.pathExistsSync(path28.join(projectDir, "yarn.lock"))) {
7830
7928
  return "yarn";
7831
7929
  }
7832
7930
  return "npm";
@@ -7890,7 +7988,7 @@ function updatePackageJsonVersions(packageJsonPath, packageJson, versions) {
7890
7988
  if (updated.length > 0) {
7891
7989
  packageJson.dependencies = deps;
7892
7990
  packageJson.devDependencies = devDeps;
7893
- fs26.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
7991
+ fs27.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
7894
7992
  }
7895
7993
  return updated;
7896
7994
  }
@@ -7900,15 +7998,15 @@ function upgradeCommand(program2) {
7900
7998
  ).option("--dry-run", "Show what would be updated without making changes").action(async (options) => {
7901
7999
  log.title("MARS Upgrade");
7902
8000
  const projectDir = process.cwd();
7903
- const packageJsonPath = path26.join(projectDir, "package.json");
7904
- if (!fs26.pathExistsSync(packageJsonPath)) {
8001
+ const packageJsonPath = path28.join(projectDir, "package.json");
8002
+ if (!fs27.pathExistsSync(packageJsonPath)) {
7905
8003
  log.error(
7906
8004
  "No package.json found. Are you in a Mars project directory?"
7907
8005
  );
7908
8006
  process.exitCode = 1;
7909
8007
  return;
7910
8008
  }
7911
- const packageJson = fs26.readJsonSync(packageJsonPath);
8009
+ const packageJson = fs27.readJsonSync(packageJsonPath);
7912
8010
  const hasMarsPackage = MARS_PACKAGES.some(
7913
8011
  (name) => readCurrentVersion(packageJson, name) !== null
7914
8012
  );
@@ -8004,7 +8102,7 @@ init_sentry();
8004
8102
  init_feature_flags();
8005
8103
  init_logger();
8006
8104
  var program = new Command();
8007
- program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version("0.1.0").option("-v, --verbose", "Enable verbose output for debugging");
8105
+ program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(getCliVersion()).option("-v, --verbose", "Enable verbose output for debugging");
8008
8106
  function isVerbose() {
8009
8107
  return program.opts().verbose === true;
8010
8108
  }