@mars-stack/cli 1.0.2 → 2.0.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.
package/dist/index.js CHANGED
@@ -1165,6 +1165,7 @@ async function generateNotifications(projectRoot) {
1165
1165
  "src/features/notifications/components/NotificationBell.tsx": notificationBell(),
1166
1166
  "src/features/notifications/components/NotificationPanel.tsx": notificationPanel(),
1167
1167
  "src/features/notifications/components/index.ts": componentIndex2(),
1168
+ "src/features/notifications/index.ts": featureIndex(),
1168
1169
  "src/app/api/protected/notifications/route.ts": listRoute(),
1169
1170
  "src/app/api/protected/notifications/[id]/read/route.ts": markReadRoute(),
1170
1171
  "src/app/api/protected/notifications/read-all/route.ts": markAllReadRoute(),
@@ -1179,18 +1180,21 @@ async function generateNotifications(projectRoot) {
1179
1180
  count++;
1180
1181
  }
1181
1182
  await addUserRelation(projectRoot, "notifications Notification[]", ctx);
1183
+ await wireProtectedNav2(projectRoot, ctx);
1182
1184
  await setConfigFlag3(projectRoot, ctx);
1183
1185
  await ctx.commit();
1184
- log.success(`Generated notifications feature with ${count} files`);
1186
+ log.success(`Generated and wired notifications feature (${count} files created)`);
1185
1187
  log.blank();
1186
1188
  log.step("prisma/schema/notification.prisma \u2014 Notification model");
1187
1189
  log.step("src/features/notifications/ \u2014 types, validation, server logic, hooks, components");
1188
1190
  log.step("src/app/api/protected/notifications/ \u2014 CRUD API routes");
1189
1191
  log.blank();
1192
+ log.step("Wired automatically:");
1193
+ log.step(" \u2713 NotificationBell added to navigation bar");
1194
+ log.blank();
1190
1195
  log.warn("Next steps:");
1191
1196
  log.step("1. Run yarn db:push to sync the Prisma schema");
1192
- log.step("2. Add <NotificationBell /> to your navbar/header");
1193
- log.step("3. Import createNotification from server to trigger notifications from other features:");
1197
+ log.step("2. Import createNotification from server to trigger notifications from other features:");
1194
1198
  log.step(' import { createNotification } from "@/features/notifications/server"');
1195
1199
  log.blank();
1196
1200
  } catch (error) {
@@ -1198,6 +1202,23 @@ async function generateNotifications(projectRoot) {
1198
1202
  throw error;
1199
1203
  }
1200
1204
  }
1205
+ async function wireProtectedNav2(projectRoot, ctx) {
1206
+ const navPath = path8.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
1207
+ if (!await fs8.pathExists(navPath)) return;
1208
+ await ctx.trackModifiedFile(navPath);
1209
+ let content = await fs8.readFile(navPath, "utf-8");
1210
+ if (content.includes("NotificationBell")) return;
1211
+ content = insertImportAfterDirectives(
1212
+ content,
1213
+ `import { NotificationBell } from '@/features/notifications';
1214
+ `
1215
+ );
1216
+ content = content.replace(
1217
+ /(<div className="flex items-center gap-3">)\s*\n(\s*)(<div className="hidden md:block">)/,
1218
+ "$1\n$2<NotificationBell />\n$2$3"
1219
+ );
1220
+ await fs8.writeFile(navPath, content);
1221
+ }
1201
1222
  async function setConfigFlag3(projectRoot, ctx) {
1202
1223
  const configPath = path8.join(projectRoot, "src", "config", "app.config.ts");
1203
1224
  if (!await fs8.pathExists(configPath)) return;
@@ -1681,6 +1702,11 @@ export function NotificationPanel({
1681
1702
  }
1682
1703
  `;
1683
1704
  }
1705
+ function featureIndex() {
1706
+ return `${STAMP3}
1707
+ export { NotificationBell, NotificationPanel } from './components';
1708
+ `;
1709
+ }
1684
1710
  function componentIndex2() {
1685
1711
  return `${STAMP3}
1686
1712
  export { NotificationBell } from './NotificationBell';
@@ -1781,6 +1807,7 @@ var GENERATOR_VERSION3, STAMP3;
1781
1807
  var init_notifications = __esm({
1782
1808
  "src/generators/features/notifications.ts"() {
1783
1809
  "use strict";
1810
+ init_client_file_patch();
1784
1811
  init_logger();
1785
1812
  init_rollback();
1786
1813
  init_prisma();
@@ -1819,15 +1846,19 @@ async function generateAnalytics(projectRoot) {
1819
1846
  await fs9.writeFile(fullPath, content);
1820
1847
  count++;
1821
1848
  }
1849
+ await wireProviders2(projectRoot, ctx);
1822
1850
  await setConfigFlag4(projectRoot, ctx);
1823
1851
  await ctx.commit();
1824
- log.success(`Generated analytics feature with ${count} files`);
1852
+ log.success(`Generated and wired analytics feature (${count} files created)`);
1825
1853
  log.blank();
1826
1854
  log.step("src/features/analytics/ \u2014 types and barrel exports");
1827
1855
  log.step("src/lib/shared/components/providers/AnalyticsProvider.tsx \u2014 provider wrapper");
1828
1856
  log.step("src/lib/shared/utils/analytics.ts \u2014 unified tracking API");
1829
1857
  log.step("src/lib/shared/components/patterns/ConsentBanner.tsx \u2014 cookie consent banner");
1830
1858
  log.blank();
1859
+ log.step("Wired automatically:");
1860
+ log.step(" \u2713 AnalyticsProvider wrapping app in providers.tsx");
1861
+ log.blank();
1831
1862
  log.warn("Install dependencies for your chosen provider:");
1832
1863
  log.step("Vercel: yarn add @vercel/analytics @vercel/speed-insights");
1833
1864
  log.step("PostHog: yarn add posthog-js");
@@ -1836,13 +1867,39 @@ async function generateAnalytics(projectRoot) {
1836
1867
  log.warn("Next steps:");
1837
1868
  log.step("Set the provider in appConfig.services.analytics.provider");
1838
1869
  log.step("Set required environment variables for the chosen provider");
1839
- log.step("Add <AnalyticsProvider> and <ConsentBanner /> to your root layout");
1840
1870
  log.blank();
1841
1871
  } catch (error) {
1842
1872
  await ctx.rollback();
1843
1873
  throw error;
1844
1874
  }
1845
1875
  }
1876
+ async function wireProviders2(projectRoot, ctx) {
1877
+ const providersPath = path9.join(projectRoot, "src", "app", "providers.tsx");
1878
+ if (!await fs9.pathExists(providersPath)) return;
1879
+ await ctx.trackModifiedFile(providersPath);
1880
+ let content = await fs9.readFile(providersPath, "utf-8");
1881
+ if (content.includes("AnalyticsProvider")) return;
1882
+ content = insertImportAfterDirectives(
1883
+ content,
1884
+ `import { AnalyticsProvider } from '@/lib/shared/components/providers/AnalyticsProvider';
1885
+ `
1886
+ );
1887
+ content = content.replace(
1888
+ /return\s+(?:\(\s*)?((?:<\w+[^>]*>)[\s\S]*?\{children\}[\s\S]*?(?:<\/\w+>))\s*(?:\)\s*)?;/,
1889
+ (_match, jsx) => {
1890
+ return `return (
1891
+ <AnalyticsProvider>${jsx}</AnalyticsProvider>
1892
+ );`;
1893
+ }
1894
+ );
1895
+ await fs9.writeFile(providersPath, content);
1896
+ const written = await fs9.readFile(providersPath, "utf-8");
1897
+ if (!written.includes("AnalyticsProvider")) {
1898
+ throw new Error(
1899
+ "wireProviders: AnalyticsProvider was not inserted into providers.tsx \u2014 the return statement pattern did not match. Review the template file structure."
1900
+ );
1901
+ }
1902
+ }
1846
1903
  async function setConfigFlag4(projectRoot, ctx) {
1847
1904
  const configPath = path9.join(projectRoot, "src", "config", "app.config.ts");
1848
1905
  if (!await fs9.pathExists(configPath)) return;
@@ -1872,7 +1929,7 @@ function analyticsProvider() {
1872
1929
  return `${STAMP4}
1873
1930
  'use client';
1874
1931
 
1875
- import { useEffect, useState } from 'react';
1932
+ import { Suspense, useEffect, useState } from 'react';
1876
1933
  import { usePathname, useSearchParams } from 'next/navigation';
1877
1934
  import Script from 'next/script';
1878
1935
  import { appConfig } from '@/config/app.config';
@@ -1895,7 +1952,9 @@ export function AnalyticsProvider({ children }: AnalyticsProviderProps) {
1895
1952
  {provider === 'vercel' && <VercelAnalyticsProvider />}
1896
1953
  {provider === 'posthog' && <PostHogAnalyticsProvider />}
1897
1954
  {provider === 'google' && <GoogleAnalyticsProvider />}
1898
- <PageViewTracker />
1955
+ <Suspense>
1956
+ <PageViewTracker />
1957
+ </Suspense>
1899
1958
  {children}
1900
1959
  </>
1901
1960
  );
@@ -2059,22 +2118,18 @@ export function trackEvent(
2059
2118
 
2060
2119
  switch (provider) {
2061
2120
  case 'vercel': {
2062
- try {
2063
- const { track } = require('@vercel/analytics');
2064
- track(eventName, properties);
2065
- } catch {
2066
- // @vercel/analytics not available
2067
- }
2121
+ const vercelSpec = '@vercel/analytics';
2122
+ import(/* webpackIgnore: true */ vercelSpec)
2123
+ .then((mod) => mod.track(eventName, properties))
2124
+ .catch(() => {});
2068
2125
  break;
2069
2126
  }
2070
2127
 
2071
2128
  case 'posthog': {
2072
- try {
2073
- const posthog = require('posthog-js').default;
2074
- posthog.capture(eventName, properties);
2075
- } catch {
2076
- // posthog-js not available
2077
- }
2129
+ const phSpec = 'posthog-js';
2130
+ import(/* webpackIgnore: true */ phSpec)
2131
+ .then((mod) => mod.default.capture(eventName, properties))
2132
+ .catch(() => {});
2078
2133
  break;
2079
2134
  }
2080
2135
 
@@ -2105,12 +2160,10 @@ export function identifyUser(
2105
2160
  break;
2106
2161
 
2107
2162
  case 'posthog': {
2108
- try {
2109
- const posthog = require('posthog-js').default;
2110
- posthog.identify(userId, traits);
2111
- } catch {
2112
- // posthog-js not available
2113
- }
2163
+ const phSpec = 'posthog-js';
2164
+ import(/* webpackIgnore: true */ phSpec)
2165
+ .then((mod) => mod.default.identify(userId, traits))
2166
+ .catch(() => {});
2114
2167
  break;
2115
2168
  }
2116
2169
 
@@ -2199,6 +2252,7 @@ var GENERATOR_VERSION4, STAMP4;
2199
2252
  var init_analytics = __esm({
2200
2253
  "src/generators/features/analytics.ts"() {
2201
2254
  "use strict";
2255
+ init_client_file_patch();
2202
2256
  init_logger();
2203
2257
  init_rollback();
2204
2258
  GENERATOR_VERSION4 = "0.1.0";
@@ -2229,7 +2283,7 @@ async function generateCommandPalette(projectRoot) {
2229
2283
  "src/features/command-palette/components/CommandPalette.tsx": commandPalette(),
2230
2284
  "src/features/command-palette/components/CommandTrigger.tsx": commandTrigger(),
2231
2285
  "src/features/command-palette/components/index.ts": componentIndex3(),
2232
- "src/features/command-palette/index.ts": featureIndex()
2286
+ "src/features/command-palette/index.ts": featureIndex2()
2233
2287
  };
2234
2288
  let count = 0;
2235
2289
  for (const [filePath, content] of Object.entries(files)) {
@@ -2240,15 +2294,17 @@ async function generateCommandPalette(projectRoot) {
2240
2294
  count++;
2241
2295
  }
2242
2296
  await addDependencies(projectRoot, { cmdk: "^1.0.0" });
2297
+ await wireLayout2(projectRoot, ctx);
2243
2298
  await setConfigFlag5(projectRoot, ctx);
2244
2299
  await ctx.commit();
2245
- log.success(`Generated command palette feature with ${count} files`);
2300
+ log.success(`Generated and wired command palette feature (${count} files created)`);
2246
2301
  log.blank();
2247
2302
  log.step("src/features/command-palette/ \u2014 types, actions, registry, recent, components");
2248
2303
  log.blank();
2249
- log.warn("Integration steps:");
2250
- log.step("Add <CommandPalette /> to your protected layout");
2251
- log.step("Optionally add <CommandTrigger /> to your navbar for discoverability");
2304
+ log.step("Wired automatically:");
2305
+ log.step(" \u2713 CommandPalette overlay mounted in root layout");
2306
+ log.step(" \u2713 Press \u2318K / Ctrl+K to open");
2307
+ log.blank();
2252
2308
  log.step("Register custom actions with registerAction() from any feature");
2253
2309
  log.blank();
2254
2310
  } catch (error) {
@@ -2256,6 +2312,20 @@ async function generateCommandPalette(projectRoot) {
2256
2312
  throw error;
2257
2313
  }
2258
2314
  }
2315
+ 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");
2320
+ if (content.includes("CommandPalette")) return;
2321
+ content = `import { CommandPalette } from '@/features/command-palette';
2322
+ ${content}`;
2323
+ content = content.replace(
2324
+ /(\s*)(<Providers>\{children\}<\/Providers>)/,
2325
+ "$1$2\n$1<CommandPalette />"
2326
+ );
2327
+ await fs10.writeFile(layoutPath, content);
2328
+ }
2259
2329
  async function setConfigFlag5(projectRoot, ctx) {
2260
2330
  const configPath = path10.join(projectRoot, "src", "config", "app.config.ts");
2261
2331
  if (!await fs10.pathExists(configPath)) return;
@@ -2652,7 +2722,7 @@ export { CommandPalette } from './CommandPalette';
2652
2722
  export { CommandTrigger } from './CommandTrigger';
2653
2723
  `;
2654
2724
  }
2655
- function featureIndex() {
2725
+ function featureIndex2() {
2656
2726
  return `${STAMP5}
2657
2727
  export { CommandPalette, CommandTrigger } from './components';
2658
2728
  export { registerAction, getCustomActions } from './registry';
@@ -2700,7 +2770,7 @@ async function generateOnboarding(projectRoot) {
2700
2770
  "src/app/(protected)/onboarding/page.tsx": onboardingPage(),
2701
2771
  "src/app/api/protected/onboarding/route.ts": getProgressRoute(),
2702
2772
  "src/app/api/protected/onboarding/step/route.ts": stepActionRoute(),
2703
- "src/features/onboarding/index.ts": featureIndex2()
2773
+ "src/features/onboarding/index.ts": featureIndex3()
2704
2774
  };
2705
2775
  let count = 0;
2706
2776
  for (const [filePath, content] of Object.entries(files)) {
@@ -3231,7 +3301,7 @@ export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
3231
3301
  });
3232
3302
  `;
3233
3303
  }
3234
- function featureIndex2() {
3304
+ function featureIndex3() {
3235
3305
  return `${STAMP6}
3236
3306
  export { ONBOARDING_STEPS } from './config';
3237
3307
  export type { OnboardingStep } from './config';
@@ -3280,7 +3350,7 @@ async function generateSearch(projectRoot) {
3280
3350
  "src/features/search/hooks/use-search.ts": useSearchHook(),
3281
3351
  "src/features/search/components/SearchInput.tsx": searchInput(),
3282
3352
  "src/features/search/components/index.ts": componentIndex5(),
3283
- "src/features/search/index.ts": featureIndex3(),
3353
+ "src/features/search/index.ts": featureIndex4(),
3284
3354
  "src/app/api/protected/search/route.ts": searchRoute()
3285
3355
  };
3286
3356
  let count = 0;
@@ -3592,7 +3662,7 @@ function componentIndex5() {
3592
3662
  export { SearchInput } from './SearchInput';
3593
3663
  `;
3594
3664
  }
3595
- function featureIndex3() {
3665
+ function featureIndex4() {
3596
3666
  return `${STAMP7}
3597
3667
  export type { SearchResult, SearchOptions, SearchProvider } from './types';
3598
3668
  export { searchParamsSchema } from './validation/schemas';
@@ -3664,7 +3734,7 @@ async function generateRealtime(projectRoot) {
3664
3734
  "src/features/realtime/server/sse.ts": sseProvider(),
3665
3735
  "src/features/realtime/hooks/use-event-source.ts": useEventSourceHook(),
3666
3736
  "src/features/realtime/hooks/index.ts": hooksIndex(),
3667
- "src/features/realtime/index.ts": featureIndex4(),
3737
+ "src/features/realtime/index.ts": featureIndex5(),
3668
3738
  "src/app/api/protected/realtime/stream/route.ts": sseStreamRoute()
3669
3739
  };
3670
3740
  let count = 0;
@@ -3912,7 +3982,7 @@ function hooksIndex() {
3912
3982
  export { useEventSource } from './use-event-source';
3913
3983
  `;
3914
3984
  }
3915
- function featureIndex4() {
3985
+ function featureIndex5() {
3916
3986
  return `${STAMP8}
3917
3987
  export type {
3918
3988
  RealtimeProvider,
@@ -4522,7 +4592,7 @@ async function generateCookieConsent(projectRoot) {
4522
4592
  "src/features/cookie-consent/components/CookieConsentBanner.tsx": cookieConsentBanner(),
4523
4593
  "src/features/cookie-consent/components/CookiePreferencesDialog.tsx": cookiePreferencesDialog(),
4524
4594
  "src/features/cookie-consent/components/index.ts": componentsIndex(),
4525
- "src/features/cookie-consent/index.ts": featureIndex5()
4595
+ "src/features/cookie-consent/index.ts": featureIndex6()
4526
4596
  };
4527
4597
  let count = 0;
4528
4598
  for (const [filePath, content] of Object.entries(files)) {
@@ -4532,17 +4602,19 @@ async function generateCookieConsent(projectRoot) {
4532
4602
  await fs15.writeFile(fullPath, content);
4533
4603
  count++;
4534
4604
  }
4605
+ await wireLayout3(projectRoot, ctx);
4535
4606
  await setConfigFlag10(projectRoot, ctx);
4536
4607
  await ctx.commit();
4537
- log.success(`Generated cookie consent feature with ${count} files`);
4608
+ log.success(`Generated and wired cookie consent feature (${count} files created)`);
4538
4609
  log.blank();
4539
4610
  log.step("src/features/cookie-consent/types.ts \u2014 ConsentStatus, ConsentPreferences");
4540
4611
  log.step("src/features/cookie-consent/hooks/use-consent.ts \u2014 useConsent hook");
4541
4612
  log.step("src/features/cookie-consent/components/CookieConsentBanner.tsx \u2014 banner");
4542
4613
  log.step("src/features/cookie-consent/components/CookiePreferencesDialog.tsx \u2014 preferences modal");
4543
4614
  log.blank();
4544
- log.warn("Complete setup:");
4545
- log.step("Add <CookieConsentBanner /> to your root layout");
4615
+ log.step("Wired automatically:");
4616
+ log.step(" \u2713 CookieConsentBanner mounted in root layout");
4617
+ log.blank();
4546
4618
  log.step("Optionally add <CookiePreferencesDialog /> for granular consent management");
4547
4619
  log.step("Check consent status with useConsent() before loading tracking scripts");
4548
4620
  log.blank();
@@ -4551,6 +4623,20 @@ async function generateCookieConsent(projectRoot) {
4551
4623
  throw error;
4552
4624
  }
4553
4625
  }
4626
+ 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");
4631
+ if (content.includes("CookieConsentBanner")) return;
4632
+ content = `import { CookieConsentBanner } from '@/features/cookie-consent';
4633
+ ${content}`;
4634
+ content = content.replace(
4635
+ /(\s*)(<Providers>\{children\}<\/Providers>)/,
4636
+ "$1$2\n$1<CookieConsentBanner />"
4637
+ );
4638
+ await fs15.writeFile(layoutPath, content);
4639
+ }
4554
4640
  async function setConfigFlag10(projectRoot, ctx) {
4555
4641
  const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
4556
4642
  if (!await fs15.pathExists(configPath)) return;
@@ -4819,7 +4905,7 @@ export { CookieConsentBanner } from './CookieConsentBanner';
4819
4905
  export { CookiePreferencesDialog } from './CookiePreferencesDialog';
4820
4906
  `;
4821
4907
  }
4822
- function featureIndex5() {
4908
+ function featureIndex6() {
4823
4909
  return `${STAMP10}
4824
4910
  export type { ConsentStatus, ConsentPreferences } from './types';
4825
4911
  export { useConsent } from './hooks/use-consent';
@@ -5292,7 +5378,7 @@ async function generateFeatureFlags(projectRoot) {
5292
5378
  "src/features/feature-flags/hooks/use-feature-flag.ts": useFeatureFlag(),
5293
5379
  "src/features/feature-flags/components/FeatureGate.tsx": featureGate(),
5294
5380
  "src/features/feature-flags/components/index.ts": componentsIndex3(),
5295
- "src/features/feature-flags/index.ts": featureIndex6(),
5381
+ "src/features/feature-flags/index.ts": featureIndex7(),
5296
5382
  "src/config/feature-flags.json": featureFlagsJson(),
5297
5383
  "src/app/api/protected/feature-flags/route.ts": apiRoute()
5298
5384
  };
@@ -5503,7 +5589,7 @@ function componentsIndex3() {
5503
5589
  export { FeatureGate } from './FeatureGate';
5504
5590
  `;
5505
5591
  }
5506
- function featureIndex6() {
5592
+ function featureIndex7() {
5507
5593
  return `${STAMP13}
5508
5594
  export { getFlag } from './server';
5509
5595
  export { useFeatureFlag } from './hooks/use-feature-flag';