@mars-stack/cli 0.2.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
|
@@ -686,6 +686,21 @@ var init_blog = __esm({
|
|
|
686
686
|
}
|
|
687
687
|
});
|
|
688
688
|
|
|
689
|
+
// src/utils/client-file-patch.ts
|
|
690
|
+
function insertImportAfterDirectives(content, lineToInsert) {
|
|
691
|
+
if (CLIENT_DIRECTIVE.test(content)) {
|
|
692
|
+
return content.replace(/^(['"]use client['"];?\s*\n)/, `$1${lineToInsert}`);
|
|
693
|
+
}
|
|
694
|
+
return lineToInsert + content;
|
|
695
|
+
}
|
|
696
|
+
var CLIENT_DIRECTIVE;
|
|
697
|
+
var init_client_file_patch = __esm({
|
|
698
|
+
"src/utils/client-file-patch.ts"() {
|
|
699
|
+
"use strict";
|
|
700
|
+
CLIENT_DIRECTIVE = /^['"]use client['"];?\s*\n/;
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
689
704
|
// src/generators/features/dark-mode.ts
|
|
690
705
|
var dark_mode_exports = {};
|
|
691
706
|
__export(dark_mode_exports, {
|
|
@@ -716,9 +731,13 @@ async function generateDarkMode(projectRoot) {
|
|
|
716
731
|
await fs6.writeFile(fullPath, content);
|
|
717
732
|
count++;
|
|
718
733
|
}
|
|
734
|
+
await wireLayout(projectRoot, ctx);
|
|
735
|
+
await wireProviders(projectRoot, ctx);
|
|
736
|
+
await wireProtectedNav(projectRoot, ctx);
|
|
737
|
+
await wireTailwindDarkMode(projectRoot, ctx);
|
|
719
738
|
await setConfigFlag2(projectRoot, ctx);
|
|
720
739
|
await ctx.commit();
|
|
721
|
-
log.success(`Generated dark mode feature
|
|
740
|
+
log.success(`Generated and wired dark mode feature (${count} files created)`);
|
|
722
741
|
log.blank();
|
|
723
742
|
log.step("src/lib/shared/components/providers/ThemeProvider.tsx \u2014 context, hook, persistence");
|
|
724
743
|
log.step("src/lib/shared/components/patterns/ThemeToggle.tsx \u2014 three-option toggle");
|
|
@@ -726,14 +745,12 @@ async function generateDarkMode(projectRoot) {
|
|
|
726
745
|
log.step("src/features/dark-mode/theme-script.ts \u2014 FOUC prevention script");
|
|
727
746
|
log.step("src/features/dark-mode/index.ts \u2014 barrel exports");
|
|
728
747
|
log.blank();
|
|
729
|
-
log.
|
|
730
|
-
log.step("
|
|
731
|
-
log.step("
|
|
732
|
-
log.step(
|
|
733
|
-
log.step("
|
|
734
|
-
log.step("
|
|
735
|
-
log.step("4. Add <ThemeToggle /> or <ThemeToggleSimple /> to your navbar");
|
|
736
|
-
log.step('5. Ensure darkMode: "class" is set in your Tailwind config');
|
|
748
|
+
log.step("Wired automatically:");
|
|
749
|
+
log.step(" \u2713 FOUC-prevention script added to <head> in layout.tsx");
|
|
750
|
+
log.step(" \u2713 suppressHydrationWarning on <html>");
|
|
751
|
+
log.step(" \u2713 ThemeProvider wrapping app in providers.tsx");
|
|
752
|
+
log.step(" \u2713 ThemeToggleSimple added to navigation bar");
|
|
753
|
+
log.step(" \u2713 Tailwind dark mode class variant configured in globals.css");
|
|
737
754
|
log.blank();
|
|
738
755
|
} catch (error) {
|
|
739
756
|
await ctx.rollback();
|
|
@@ -748,6 +765,84 @@ async function setConfigFlag2(projectRoot, ctx) {
|
|
|
748
765
|
const updated = content.replace(/darkMode:\s*false/, "darkMode: true");
|
|
749
766
|
await fs6.writeFile(configPath, updated);
|
|
750
767
|
}
|
|
768
|
+
async function wireLayout(projectRoot, ctx) {
|
|
769
|
+
const layoutPath = path6.join(projectRoot, "src", "app", "layout.tsx");
|
|
770
|
+
if (!await fs6.pathExists(layoutPath)) return;
|
|
771
|
+
await ctx.trackModifiedFile(layoutPath);
|
|
772
|
+
let content = await fs6.readFile(layoutPath, "utf-8");
|
|
773
|
+
if (!content.includes("getThemeScript")) {
|
|
774
|
+
content = `import { getThemeScript } from '@/features/dark-mode';
|
|
775
|
+
${content}`;
|
|
776
|
+
}
|
|
777
|
+
if (!content.includes("suppressHydrationWarning")) {
|
|
778
|
+
content = content.replace(/<html\s+lang="en"/, '<html lang="en" suppressHydrationWarning');
|
|
779
|
+
}
|
|
780
|
+
if (!content.includes("getThemeScript()")) {
|
|
781
|
+
content = content.replace(
|
|
782
|
+
/(<html[^>]*>)\s*\n(\s*<body)/,
|
|
783
|
+
"$1\n <head>\n <script dangerouslySetInnerHTML={{ __html: getThemeScript() }} />\n </head>\n$2"
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
await fs6.writeFile(layoutPath, content);
|
|
787
|
+
}
|
|
788
|
+
async function wireProviders(projectRoot, ctx) {
|
|
789
|
+
const providersPath = path6.join(projectRoot, "src", "app", "providers.tsx");
|
|
790
|
+
if (!await fs6.pathExists(providersPath)) return;
|
|
791
|
+
await ctx.trackModifiedFile(providersPath);
|
|
792
|
+
let content = await fs6.readFile(providersPath, "utf-8");
|
|
793
|
+
if (content.includes("ThemeProvider")) return;
|
|
794
|
+
content = insertImportAfterDirectives(
|
|
795
|
+
content,
|
|
796
|
+
`import { ThemeProvider } from '@/features/dark-mode';
|
|
797
|
+
`
|
|
798
|
+
);
|
|
799
|
+
content = content.replace(
|
|
800
|
+
/return\s+(?:\(\s*)?((?:<\w+[^>]*>)[\s\S]*?\{children\}[\s\S]*?(?:<\/\w+>))\s*(?:\)\s*)?;/,
|
|
801
|
+
(_match, jsx) => {
|
|
802
|
+
const wrapped = jsx.replace(
|
|
803
|
+
/(\{children\})/,
|
|
804
|
+
"<ThemeProvider>$1</ThemeProvider>"
|
|
805
|
+
);
|
|
806
|
+
return `return (
|
|
807
|
+
${wrapped}
|
|
808
|
+
);`;
|
|
809
|
+
}
|
|
810
|
+
);
|
|
811
|
+
await fs6.writeFile(providersPath, content);
|
|
812
|
+
}
|
|
813
|
+
async function wireProtectedNav(projectRoot, ctx) {
|
|
814
|
+
const navPath = path6.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
815
|
+
if (!await fs6.pathExists(navPath)) return;
|
|
816
|
+
await ctx.trackModifiedFile(navPath);
|
|
817
|
+
let content = await fs6.readFile(navPath, "utf-8");
|
|
818
|
+
if (content.includes("ThemeToggleSimple")) return;
|
|
819
|
+
content = insertImportAfterDirectives(
|
|
820
|
+
content,
|
|
821
|
+
`import { ThemeToggleSimple } from '@/features/dark-mode';
|
|
822
|
+
`
|
|
823
|
+
);
|
|
824
|
+
content = content.replace(
|
|
825
|
+
/(<div className="hidden md:block">)\s*\n(\s*)(<UserMenu \/>)/,
|
|
826
|
+
"$1\n$2<ThemeToggleSimple />\n$2$3"
|
|
827
|
+
);
|
|
828
|
+
content = content.replace(
|
|
829
|
+
/(<div className="border-t border-border-default px-4 py-3">\s*\n\s*<UserMenu \/>)/,
|
|
830
|
+
'$1\n <div className="mt-2">\n <ThemeToggleSimple />\n </div>'
|
|
831
|
+
);
|
|
832
|
+
await fs6.writeFile(navPath, content);
|
|
833
|
+
}
|
|
834
|
+
async function wireTailwindDarkMode(projectRoot, ctx) {
|
|
835
|
+
const globalsPath = path6.join(projectRoot, "src", "styles", "globals.css");
|
|
836
|
+
if (!await fs6.pathExists(globalsPath)) return;
|
|
837
|
+
await ctx.trackModifiedFile(globalsPath);
|
|
838
|
+
let content = await fs6.readFile(globalsPath, "utf-8");
|
|
839
|
+
if (content.includes("@custom-variant dark")) return;
|
|
840
|
+
content = content.replace(
|
|
841
|
+
/@import 'tailwindcss';/,
|
|
842
|
+
"@import 'tailwindcss';\n@custom-variant dark (&:where(.dark, .dark *));"
|
|
843
|
+
);
|
|
844
|
+
await fs6.writeFile(globalsPath, content);
|
|
845
|
+
}
|
|
751
846
|
function themeProvider() {
|
|
752
847
|
return `${STAMP2}
|
|
753
848
|
'use client';
|
|
@@ -1017,9 +1112,10 @@ var GENERATOR_VERSION2, STAMP2;
|
|
|
1017
1112
|
var init_dark_mode = __esm({
|
|
1018
1113
|
"src/generators/features/dark-mode.ts"() {
|
|
1019
1114
|
"use strict";
|
|
1115
|
+
init_client_file_patch();
|
|
1020
1116
|
init_logger();
|
|
1021
1117
|
init_rollback();
|
|
1022
|
-
GENERATOR_VERSION2 = "0.
|
|
1118
|
+
GENERATOR_VERSION2 = "0.2.0";
|
|
1023
1119
|
STAMP2 = `// @mars-generated dark-mode@${GENERATOR_VERSION2}`;
|
|
1024
1120
|
}
|
|
1025
1121
|
});
|
|
@@ -1069,6 +1165,7 @@ async function generateNotifications(projectRoot) {
|
|
|
1069
1165
|
"src/features/notifications/components/NotificationBell.tsx": notificationBell(),
|
|
1070
1166
|
"src/features/notifications/components/NotificationPanel.tsx": notificationPanel(),
|
|
1071
1167
|
"src/features/notifications/components/index.ts": componentIndex2(),
|
|
1168
|
+
"src/features/notifications/index.ts": featureIndex(),
|
|
1072
1169
|
"src/app/api/protected/notifications/route.ts": listRoute(),
|
|
1073
1170
|
"src/app/api/protected/notifications/[id]/read/route.ts": markReadRoute(),
|
|
1074
1171
|
"src/app/api/protected/notifications/read-all/route.ts": markAllReadRoute(),
|
|
@@ -1083,18 +1180,21 @@ async function generateNotifications(projectRoot) {
|
|
|
1083
1180
|
count++;
|
|
1084
1181
|
}
|
|
1085
1182
|
await addUserRelation(projectRoot, "notifications Notification[]", ctx);
|
|
1183
|
+
await wireProtectedNav2(projectRoot, ctx);
|
|
1086
1184
|
await setConfigFlag3(projectRoot, ctx);
|
|
1087
1185
|
await ctx.commit();
|
|
1088
|
-
log.success(`Generated notifications feature
|
|
1186
|
+
log.success(`Generated and wired notifications feature (${count} files created)`);
|
|
1089
1187
|
log.blank();
|
|
1090
1188
|
log.step("prisma/schema/notification.prisma \u2014 Notification model");
|
|
1091
1189
|
log.step("src/features/notifications/ \u2014 types, validation, server logic, hooks, components");
|
|
1092
1190
|
log.step("src/app/api/protected/notifications/ \u2014 CRUD API routes");
|
|
1093
1191
|
log.blank();
|
|
1192
|
+
log.step("Wired automatically:");
|
|
1193
|
+
log.step(" \u2713 NotificationBell added to navigation bar");
|
|
1194
|
+
log.blank();
|
|
1094
1195
|
log.warn("Next steps:");
|
|
1095
1196
|
log.step("1. Run yarn db:push to sync the Prisma schema");
|
|
1096
|
-
log.step("2.
|
|
1097
|
-
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:");
|
|
1098
1198
|
log.step(' import { createNotification } from "@/features/notifications/server"');
|
|
1099
1199
|
log.blank();
|
|
1100
1200
|
} catch (error) {
|
|
@@ -1102,6 +1202,23 @@ async function generateNotifications(projectRoot) {
|
|
|
1102
1202
|
throw error;
|
|
1103
1203
|
}
|
|
1104
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
|
+
}
|
|
1105
1222
|
async function setConfigFlag3(projectRoot, ctx) {
|
|
1106
1223
|
const configPath = path8.join(projectRoot, "src", "config", "app.config.ts");
|
|
1107
1224
|
if (!await fs8.pathExists(configPath)) return;
|
|
@@ -1585,6 +1702,11 @@ export function NotificationPanel({
|
|
|
1585
1702
|
}
|
|
1586
1703
|
`;
|
|
1587
1704
|
}
|
|
1705
|
+
function featureIndex() {
|
|
1706
|
+
return `${STAMP3}
|
|
1707
|
+
export { NotificationBell, NotificationPanel } from './components';
|
|
1708
|
+
`;
|
|
1709
|
+
}
|
|
1588
1710
|
function componentIndex2() {
|
|
1589
1711
|
return `${STAMP3}
|
|
1590
1712
|
export { NotificationBell } from './NotificationBell';
|
|
@@ -1685,6 +1807,7 @@ var GENERATOR_VERSION3, STAMP3;
|
|
|
1685
1807
|
var init_notifications = __esm({
|
|
1686
1808
|
"src/generators/features/notifications.ts"() {
|
|
1687
1809
|
"use strict";
|
|
1810
|
+
init_client_file_patch();
|
|
1688
1811
|
init_logger();
|
|
1689
1812
|
init_rollback();
|
|
1690
1813
|
init_prisma();
|
|
@@ -1723,15 +1846,19 @@ async function generateAnalytics(projectRoot) {
|
|
|
1723
1846
|
await fs9.writeFile(fullPath, content);
|
|
1724
1847
|
count++;
|
|
1725
1848
|
}
|
|
1849
|
+
await wireProviders2(projectRoot, ctx);
|
|
1726
1850
|
await setConfigFlag4(projectRoot, ctx);
|
|
1727
1851
|
await ctx.commit();
|
|
1728
|
-
log.success(`Generated analytics feature
|
|
1852
|
+
log.success(`Generated and wired analytics feature (${count} files created)`);
|
|
1729
1853
|
log.blank();
|
|
1730
1854
|
log.step("src/features/analytics/ \u2014 types and barrel exports");
|
|
1731
1855
|
log.step("src/lib/shared/components/providers/AnalyticsProvider.tsx \u2014 provider wrapper");
|
|
1732
1856
|
log.step("src/lib/shared/utils/analytics.ts \u2014 unified tracking API");
|
|
1733
1857
|
log.step("src/lib/shared/components/patterns/ConsentBanner.tsx \u2014 cookie consent banner");
|
|
1734
1858
|
log.blank();
|
|
1859
|
+
log.step("Wired automatically:");
|
|
1860
|
+
log.step(" \u2713 AnalyticsProvider wrapping app in providers.tsx");
|
|
1861
|
+
log.blank();
|
|
1735
1862
|
log.warn("Install dependencies for your chosen provider:");
|
|
1736
1863
|
log.step("Vercel: yarn add @vercel/analytics @vercel/speed-insights");
|
|
1737
1864
|
log.step("PostHog: yarn add posthog-js");
|
|
@@ -1740,13 +1867,39 @@ async function generateAnalytics(projectRoot) {
|
|
|
1740
1867
|
log.warn("Next steps:");
|
|
1741
1868
|
log.step("Set the provider in appConfig.services.analytics.provider");
|
|
1742
1869
|
log.step("Set required environment variables for the chosen provider");
|
|
1743
|
-
log.step("Add <AnalyticsProvider> and <ConsentBanner /> to your root layout");
|
|
1744
1870
|
log.blank();
|
|
1745
1871
|
} catch (error) {
|
|
1746
1872
|
await ctx.rollback();
|
|
1747
1873
|
throw error;
|
|
1748
1874
|
}
|
|
1749
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
|
+
}
|
|
1750
1903
|
async function setConfigFlag4(projectRoot, ctx) {
|
|
1751
1904
|
const configPath = path9.join(projectRoot, "src", "config", "app.config.ts");
|
|
1752
1905
|
if (!await fs9.pathExists(configPath)) return;
|
|
@@ -1776,7 +1929,7 @@ function analyticsProvider() {
|
|
|
1776
1929
|
return `${STAMP4}
|
|
1777
1930
|
'use client';
|
|
1778
1931
|
|
|
1779
|
-
import { useEffect, useState } from 'react';
|
|
1932
|
+
import { Suspense, useEffect, useState } from 'react';
|
|
1780
1933
|
import { usePathname, useSearchParams } from 'next/navigation';
|
|
1781
1934
|
import Script from 'next/script';
|
|
1782
1935
|
import { appConfig } from '@/config/app.config';
|
|
@@ -1799,7 +1952,9 @@ export function AnalyticsProvider({ children }: AnalyticsProviderProps) {
|
|
|
1799
1952
|
{provider === 'vercel' && <VercelAnalyticsProvider />}
|
|
1800
1953
|
{provider === 'posthog' && <PostHogAnalyticsProvider />}
|
|
1801
1954
|
{provider === 'google' && <GoogleAnalyticsProvider />}
|
|
1802
|
-
<
|
|
1955
|
+
<Suspense>
|
|
1956
|
+
<PageViewTracker />
|
|
1957
|
+
</Suspense>
|
|
1803
1958
|
{children}
|
|
1804
1959
|
</>
|
|
1805
1960
|
);
|
|
@@ -1963,22 +2118,18 @@ export function trackEvent(
|
|
|
1963
2118
|
|
|
1964
2119
|
switch (provider) {
|
|
1965
2120
|
case 'vercel': {
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
track(eventName, properties)
|
|
1969
|
-
|
|
1970
|
-
// @vercel/analytics not available
|
|
1971
|
-
}
|
|
2121
|
+
const vercelSpec = '@vercel/analytics';
|
|
2122
|
+
import(/* webpackIgnore: true */ vercelSpec)
|
|
2123
|
+
.then((mod) => mod.track(eventName, properties))
|
|
2124
|
+
.catch(() => {});
|
|
1972
2125
|
break;
|
|
1973
2126
|
}
|
|
1974
2127
|
|
|
1975
2128
|
case 'posthog': {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
// posthog-js not available
|
|
1981
|
-
}
|
|
2129
|
+
const phSpec = 'posthog-js';
|
|
2130
|
+
import(/* webpackIgnore: true */ phSpec)
|
|
2131
|
+
.then((mod) => mod.default.capture(eventName, properties))
|
|
2132
|
+
.catch(() => {});
|
|
1982
2133
|
break;
|
|
1983
2134
|
}
|
|
1984
2135
|
|
|
@@ -2009,12 +2160,10 @@ export function identifyUser(
|
|
|
2009
2160
|
break;
|
|
2010
2161
|
|
|
2011
2162
|
case 'posthog': {
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
// posthog-js not available
|
|
2017
|
-
}
|
|
2163
|
+
const phSpec = 'posthog-js';
|
|
2164
|
+
import(/* webpackIgnore: true */ phSpec)
|
|
2165
|
+
.then((mod) => mod.default.identify(userId, traits))
|
|
2166
|
+
.catch(() => {});
|
|
2018
2167
|
break;
|
|
2019
2168
|
}
|
|
2020
2169
|
|
|
@@ -2103,6 +2252,7 @@ var GENERATOR_VERSION4, STAMP4;
|
|
|
2103
2252
|
var init_analytics = __esm({
|
|
2104
2253
|
"src/generators/features/analytics.ts"() {
|
|
2105
2254
|
"use strict";
|
|
2255
|
+
init_client_file_patch();
|
|
2106
2256
|
init_logger();
|
|
2107
2257
|
init_rollback();
|
|
2108
2258
|
GENERATOR_VERSION4 = "0.1.0";
|
|
@@ -2133,7 +2283,7 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2133
2283
|
"src/features/command-palette/components/CommandPalette.tsx": commandPalette(),
|
|
2134
2284
|
"src/features/command-palette/components/CommandTrigger.tsx": commandTrigger(),
|
|
2135
2285
|
"src/features/command-palette/components/index.ts": componentIndex3(),
|
|
2136
|
-
"src/features/command-palette/index.ts":
|
|
2286
|
+
"src/features/command-palette/index.ts": featureIndex2()
|
|
2137
2287
|
};
|
|
2138
2288
|
let count = 0;
|
|
2139
2289
|
for (const [filePath, content] of Object.entries(files)) {
|
|
@@ -2144,15 +2294,17 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2144
2294
|
count++;
|
|
2145
2295
|
}
|
|
2146
2296
|
await addDependencies(projectRoot, { cmdk: "^1.0.0" });
|
|
2297
|
+
await wireLayout2(projectRoot, ctx);
|
|
2147
2298
|
await setConfigFlag5(projectRoot, ctx);
|
|
2148
2299
|
await ctx.commit();
|
|
2149
|
-
log.success(`Generated command palette feature
|
|
2300
|
+
log.success(`Generated and wired command palette feature (${count} files created)`);
|
|
2150
2301
|
log.blank();
|
|
2151
2302
|
log.step("src/features/command-palette/ \u2014 types, actions, registry, recent, components");
|
|
2152
2303
|
log.blank();
|
|
2153
|
-
log.
|
|
2154
|
-
log.step("
|
|
2155
|
-
log.step("
|
|
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();
|
|
2156
2308
|
log.step("Register custom actions with registerAction() from any feature");
|
|
2157
2309
|
log.blank();
|
|
2158
2310
|
} catch (error) {
|
|
@@ -2160,6 +2312,20 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2160
2312
|
throw error;
|
|
2161
2313
|
}
|
|
2162
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
|
+
}
|
|
2163
2329
|
async function setConfigFlag5(projectRoot, ctx) {
|
|
2164
2330
|
const configPath = path10.join(projectRoot, "src", "config", "app.config.ts");
|
|
2165
2331
|
if (!await fs10.pathExists(configPath)) return;
|
|
@@ -2556,7 +2722,7 @@ export { CommandPalette } from './CommandPalette';
|
|
|
2556
2722
|
export { CommandTrigger } from './CommandTrigger';
|
|
2557
2723
|
`;
|
|
2558
2724
|
}
|
|
2559
|
-
function
|
|
2725
|
+
function featureIndex2() {
|
|
2560
2726
|
return `${STAMP5}
|
|
2561
2727
|
export { CommandPalette, CommandTrigger } from './components';
|
|
2562
2728
|
export { registerAction, getCustomActions } from './registry';
|
|
@@ -2604,7 +2770,7 @@ async function generateOnboarding(projectRoot) {
|
|
|
2604
2770
|
"src/app/(protected)/onboarding/page.tsx": onboardingPage(),
|
|
2605
2771
|
"src/app/api/protected/onboarding/route.ts": getProgressRoute(),
|
|
2606
2772
|
"src/app/api/protected/onboarding/step/route.ts": stepActionRoute(),
|
|
2607
|
-
"src/features/onboarding/index.ts":
|
|
2773
|
+
"src/features/onboarding/index.ts": featureIndex3()
|
|
2608
2774
|
};
|
|
2609
2775
|
let count = 0;
|
|
2610
2776
|
for (const [filePath, content] of Object.entries(files)) {
|
|
@@ -3135,7 +3301,7 @@ export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
|
|
|
3135
3301
|
});
|
|
3136
3302
|
`;
|
|
3137
3303
|
}
|
|
3138
|
-
function
|
|
3304
|
+
function featureIndex3() {
|
|
3139
3305
|
return `${STAMP6}
|
|
3140
3306
|
export { ONBOARDING_STEPS } from './config';
|
|
3141
3307
|
export type { OnboardingStep } from './config';
|
|
@@ -3184,7 +3350,7 @@ async function generateSearch(projectRoot) {
|
|
|
3184
3350
|
"src/features/search/hooks/use-search.ts": useSearchHook(),
|
|
3185
3351
|
"src/features/search/components/SearchInput.tsx": searchInput(),
|
|
3186
3352
|
"src/features/search/components/index.ts": componentIndex5(),
|
|
3187
|
-
"src/features/search/index.ts":
|
|
3353
|
+
"src/features/search/index.ts": featureIndex4(),
|
|
3188
3354
|
"src/app/api/protected/search/route.ts": searchRoute()
|
|
3189
3355
|
};
|
|
3190
3356
|
let count = 0;
|
|
@@ -3496,7 +3662,7 @@ function componentIndex5() {
|
|
|
3496
3662
|
export { SearchInput } from './SearchInput';
|
|
3497
3663
|
`;
|
|
3498
3664
|
}
|
|
3499
|
-
function
|
|
3665
|
+
function featureIndex4() {
|
|
3500
3666
|
return `${STAMP7}
|
|
3501
3667
|
export type { SearchResult, SearchOptions, SearchProvider } from './types';
|
|
3502
3668
|
export { searchParamsSchema } from './validation/schemas';
|
|
@@ -3568,7 +3734,7 @@ async function generateRealtime(projectRoot) {
|
|
|
3568
3734
|
"src/features/realtime/server/sse.ts": sseProvider(),
|
|
3569
3735
|
"src/features/realtime/hooks/use-event-source.ts": useEventSourceHook(),
|
|
3570
3736
|
"src/features/realtime/hooks/index.ts": hooksIndex(),
|
|
3571
|
-
"src/features/realtime/index.ts":
|
|
3737
|
+
"src/features/realtime/index.ts": featureIndex5(),
|
|
3572
3738
|
"src/app/api/protected/realtime/stream/route.ts": sseStreamRoute()
|
|
3573
3739
|
};
|
|
3574
3740
|
let count = 0;
|
|
@@ -3816,7 +3982,7 @@ function hooksIndex() {
|
|
|
3816
3982
|
export { useEventSource } from './use-event-source';
|
|
3817
3983
|
`;
|
|
3818
3984
|
}
|
|
3819
|
-
function
|
|
3985
|
+
function featureIndex5() {
|
|
3820
3986
|
return `${STAMP8}
|
|
3821
3987
|
export type {
|
|
3822
3988
|
RealtimeProvider,
|
|
@@ -4426,7 +4592,7 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4426
4592
|
"src/features/cookie-consent/components/CookieConsentBanner.tsx": cookieConsentBanner(),
|
|
4427
4593
|
"src/features/cookie-consent/components/CookiePreferencesDialog.tsx": cookiePreferencesDialog(),
|
|
4428
4594
|
"src/features/cookie-consent/components/index.ts": componentsIndex(),
|
|
4429
|
-
"src/features/cookie-consent/index.ts":
|
|
4595
|
+
"src/features/cookie-consent/index.ts": featureIndex6()
|
|
4430
4596
|
};
|
|
4431
4597
|
let count = 0;
|
|
4432
4598
|
for (const [filePath, content] of Object.entries(files)) {
|
|
@@ -4436,17 +4602,19 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4436
4602
|
await fs15.writeFile(fullPath, content);
|
|
4437
4603
|
count++;
|
|
4438
4604
|
}
|
|
4605
|
+
await wireLayout3(projectRoot, ctx);
|
|
4439
4606
|
await setConfigFlag10(projectRoot, ctx);
|
|
4440
4607
|
await ctx.commit();
|
|
4441
|
-
log.success(`Generated cookie consent feature
|
|
4608
|
+
log.success(`Generated and wired cookie consent feature (${count} files created)`);
|
|
4442
4609
|
log.blank();
|
|
4443
4610
|
log.step("src/features/cookie-consent/types.ts \u2014 ConsentStatus, ConsentPreferences");
|
|
4444
4611
|
log.step("src/features/cookie-consent/hooks/use-consent.ts \u2014 useConsent hook");
|
|
4445
4612
|
log.step("src/features/cookie-consent/components/CookieConsentBanner.tsx \u2014 banner");
|
|
4446
4613
|
log.step("src/features/cookie-consent/components/CookiePreferencesDialog.tsx \u2014 preferences modal");
|
|
4447
4614
|
log.blank();
|
|
4448
|
-
log.
|
|
4449
|
-
log.step("
|
|
4615
|
+
log.step("Wired automatically:");
|
|
4616
|
+
log.step(" \u2713 CookieConsentBanner mounted in root layout");
|
|
4617
|
+
log.blank();
|
|
4450
4618
|
log.step("Optionally add <CookiePreferencesDialog /> for granular consent management");
|
|
4451
4619
|
log.step("Check consent status with useConsent() before loading tracking scripts");
|
|
4452
4620
|
log.blank();
|
|
@@ -4455,6 +4623,20 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4455
4623
|
throw error;
|
|
4456
4624
|
}
|
|
4457
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
|
+
}
|
|
4458
4640
|
async function setConfigFlag10(projectRoot, ctx) {
|
|
4459
4641
|
const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
|
|
4460
4642
|
if (!await fs15.pathExists(configPath)) return;
|
|
@@ -4723,7 +4905,7 @@ export { CookieConsentBanner } from './CookieConsentBanner';
|
|
|
4723
4905
|
export { CookiePreferencesDialog } from './CookiePreferencesDialog';
|
|
4724
4906
|
`;
|
|
4725
4907
|
}
|
|
4726
|
-
function
|
|
4908
|
+
function featureIndex6() {
|
|
4727
4909
|
return `${STAMP10}
|
|
4728
4910
|
export type { ConsentStatus, ConsentPreferences } from './types';
|
|
4729
4911
|
export { useConsent } from './hooks/use-consent';
|
|
@@ -5196,7 +5378,7 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5196
5378
|
"src/features/feature-flags/hooks/use-feature-flag.ts": useFeatureFlag(),
|
|
5197
5379
|
"src/features/feature-flags/components/FeatureGate.tsx": featureGate(),
|
|
5198
5380
|
"src/features/feature-flags/components/index.ts": componentsIndex3(),
|
|
5199
|
-
"src/features/feature-flags/index.ts":
|
|
5381
|
+
"src/features/feature-flags/index.ts": featureIndex7(),
|
|
5200
5382
|
"src/config/feature-flags.json": featureFlagsJson(),
|
|
5201
5383
|
"src/app/api/protected/feature-flags/route.ts": apiRoute()
|
|
5202
5384
|
};
|
|
@@ -5407,7 +5589,7 @@ function componentsIndex3() {
|
|
|
5407
5589
|
export { FeatureGate } from './FeatureGate';
|
|
5408
5590
|
`;
|
|
5409
5591
|
}
|
|
5410
|
-
function
|
|
5592
|
+
function featureIndex7() {
|
|
5411
5593
|
return `${STAMP13}
|
|
5412
5594
|
export { getFlag } from './server';
|
|
5413
5595
|
export { useFeatureFlag } from './hooks/use-feature-flag';
|
|
@@ -5901,6 +6083,7 @@ async function promptTheme() {
|
|
|
5901
6083
|
init_logger();
|
|
5902
6084
|
import fs2 from "fs-extra";
|
|
5903
6085
|
import path2 from "path";
|
|
6086
|
+
import { generateBrandCss } from "@mars-stack/ui/utils";
|
|
5904
6087
|
|
|
5905
6088
|
// src/generators/app-config.ts
|
|
5906
6089
|
function escapeQuotes(value) {
|
|
@@ -6374,10 +6557,24 @@ async function scaffoldProject(targetDir, config) {
|
|
|
6374
6557
|
await fs2.writeFile(path2.join(targetDir, ".env"), generateEnvFile(config));
|
|
6375
6558
|
await fs2.writeFile(path2.join(targetDir, ".env.example"), generateEnvExample(config));
|
|
6376
6559
|
await fs2.writeFile(path2.join(targetDir, ".gitignore"), generateGitignore());
|
|
6377
|
-
await patchGlobalsCssForScaffoldedProject(targetDir);
|
|
6560
|
+
await patchGlobalsCssForScaffoldedProject(targetDir, config);
|
|
6561
|
+
await generateBrandCssForProject(targetDir, config);
|
|
6378
6562
|
return { fileCount };
|
|
6379
6563
|
}
|
|
6380
|
-
async function
|
|
6564
|
+
async function generateBrandCssForProject(targetDir, config) {
|
|
6565
|
+
const brandPath = path2.join(targetDir, "src", "styles", "brand.css");
|
|
6566
|
+
if (!await fs2.pathExists(brandPath)) return;
|
|
6567
|
+
const css = generateBrandCss(config.theme.primaryColor);
|
|
6568
|
+
await fs2.writeFile(brandPath, css);
|
|
6569
|
+
}
|
|
6570
|
+
var VALID_DESIGN_DIRECTIONS = [
|
|
6571
|
+
"modern-saas",
|
|
6572
|
+
"minimal",
|
|
6573
|
+
"enterprise",
|
|
6574
|
+
"creative",
|
|
6575
|
+
"dashboard"
|
|
6576
|
+
];
|
|
6577
|
+
async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
6381
6578
|
const globalsPath = path2.join(targetDir, "src", "styles", "globals.css");
|
|
6382
6579
|
if (!await fs2.pathExists(globalsPath)) return;
|
|
6383
6580
|
let content = await fs2.readFile(globalsPath, "utf-8");
|
|
@@ -6385,6 +6582,20 @@ async function patchGlobalsCssForScaffoldedProject(targetDir) {
|
|
|
6385
6582
|
'@source "../../../node_modules/@mars-stack/ui/dist/**/*.js";',
|
|
6386
6583
|
'@source "../../node_modules/@mars-stack/ui/dist/**/*.js";'
|
|
6387
6584
|
);
|
|
6585
|
+
const direction = VALID_DESIGN_DIRECTIONS.includes(
|
|
6586
|
+
config.theme.designDirection
|
|
6587
|
+
) ? config.theme.designDirection : "modern-saas";
|
|
6588
|
+
const currentMetaImport = /@import\s+['"]@mars-stack\/ui\/styles\/meta-[^'"]+['"]\s*;/;
|
|
6589
|
+
const newImport = `@import '@mars-stack/ui/styles/meta-${direction}.css';`;
|
|
6590
|
+
if (currentMetaImport.test(content)) {
|
|
6591
|
+
content = content.replace(currentMetaImport, newImport);
|
|
6592
|
+
} else {
|
|
6593
|
+
content = content.replace(
|
|
6594
|
+
"@import '@mars-stack/ui/styles/index.css';",
|
|
6595
|
+
`@import '@mars-stack/ui/styles/index.css';
|
|
6596
|
+
${newImport}`
|
|
6597
|
+
);
|
|
6598
|
+
}
|
|
6388
6599
|
await fs2.writeFile(globalsPath, content);
|
|
6389
6600
|
}
|
|
6390
6601
|
|