@mars-stack/cli 1.0.2 → 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 +539 -355
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/template/package.json +4 -4
- package/template/src/app/(auth)/forgotten-password/page.tsx +18 -0
- package/template/src/app/(auth)/register/page.tsx +3 -0
- package/template/src/app/(auth)/verify/page.tsx +19 -0
- package/template/src/app/(protected)/layout.tsx +18 -0
- package/template/src/app/api/auth/forgot/route.ts +7 -1
- package/template/src/app/api/auth/signup/route.ts +9 -0
- package/template/src/config/app.config.ts +0 -8
- package/template/src/config/routes.ts +2 -0
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
|
|
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 =
|
|
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 =
|
|
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
|
|
112
|
+
import path5 from "path";
|
|
113
113
|
async function addDependencies(projectRoot, deps, dev = false) {
|
|
114
|
-
const pkgPath =
|
|
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
|
|
133
|
-
import
|
|
156
|
+
import fs6 from "fs-extra";
|
|
157
|
+
import path7 from "path";
|
|
134
158
|
async function generateBlog(projectRoot) {
|
|
135
|
-
const featureDir =
|
|
136
|
-
if (await
|
|
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 =
|
|
181
|
+
const fullPath = path7.join(projectRoot, filePath);
|
|
158
182
|
ctx.trackCreatedFile(fullPath);
|
|
159
|
-
await
|
|
160
|
-
await
|
|
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 =
|
|
187
|
-
if (!await
|
|
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
|
|
214
|
+
const content = await fs6.readFile(configPath, "utf-8");
|
|
190
215
|
const updated = content.replace(/blog:\s*false/, "blog: true");
|
|
191
|
-
await
|
|
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
|
|
710
|
-
import
|
|
735
|
+
import fs7 from "fs-extra";
|
|
736
|
+
import path8 from "path";
|
|
711
737
|
async function generateDarkMode(projectRoot) {
|
|
712
|
-
const featureDir =
|
|
713
|
-
if (await
|
|
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 =
|
|
754
|
+
const fullPath = path8.join(projectRoot, filePath);
|
|
729
755
|
ctx.trackCreatedFile(fullPath);
|
|
730
|
-
await
|
|
731
|
-
await
|
|
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 =
|
|
762
|
-
if (!await
|
|
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
|
|
790
|
+
const content = await fs7.readFile(configPath, "utf-8");
|
|
765
791
|
const updated = content.replace(/darkMode:\s*false/, "darkMode: true");
|
|
766
|
-
await
|
|
792
|
+
await fs7.writeFile(configPath, updated);
|
|
767
793
|
}
|
|
768
794
|
async function wireLayout(projectRoot, ctx) {
|
|
769
|
-
const layoutPath =
|
|
770
|
-
if (!await
|
|
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
|
|
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
|
|
812
|
+
await fs7.writeFile(layoutPath, content);
|
|
787
813
|
}
|
|
788
814
|
async function wireProviders(projectRoot, ctx) {
|
|
789
|
-
const providersPath =
|
|
790
|
-
if (!await
|
|
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
|
|
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
|
|
837
|
+
await fs7.writeFile(providersPath, content);
|
|
812
838
|
}
|
|
813
839
|
async function wireProtectedNav(projectRoot, ctx) {
|
|
814
|
-
const navPath =
|
|
815
|
-
if (!await
|
|
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
|
|
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
|
|
858
|
+
await fs7.writeFile(navPath, content);
|
|
833
859
|
}
|
|
834
860
|
async function wireTailwindDarkMode(projectRoot, ctx) {
|
|
835
|
-
const globalsPath =
|
|
836
|
-
if (!await
|
|
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
|
|
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
|
|
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
|
|
1125
|
-
import
|
|
1150
|
+
import fs8 from "fs-extra";
|
|
1151
|
+
import path9 from "path";
|
|
1126
1152
|
async function addUserRelation(projectRoot, field, ctx) {
|
|
1127
|
-
const authPath =
|
|
1128
|
-
if (!await
|
|
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
|
|
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
|
|
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
|
|
1150
|
-
import
|
|
1175
|
+
import fs9 from "fs-extra";
|
|
1176
|
+
import path10 from "path";
|
|
1151
1177
|
async function generateNotifications(projectRoot) {
|
|
1152
|
-
const featureDir =
|
|
1153
|
-
if (await
|
|
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
|
}
|
|
@@ -1165,6 +1191,7 @@ async function generateNotifications(projectRoot) {
|
|
|
1165
1191
|
"src/features/notifications/components/NotificationBell.tsx": notificationBell(),
|
|
1166
1192
|
"src/features/notifications/components/NotificationPanel.tsx": notificationPanel(),
|
|
1167
1193
|
"src/features/notifications/components/index.ts": componentIndex2(),
|
|
1194
|
+
"src/features/notifications/index.ts": featureIndex(),
|
|
1168
1195
|
"src/app/api/protected/notifications/route.ts": listRoute(),
|
|
1169
1196
|
"src/app/api/protected/notifications/[id]/read/route.ts": markReadRoute(),
|
|
1170
1197
|
"src/app/api/protected/notifications/read-all/route.ts": markAllReadRoute(),
|
|
@@ -1172,25 +1199,28 @@ async function generateNotifications(projectRoot) {
|
|
|
1172
1199
|
};
|
|
1173
1200
|
let count = 0;
|
|
1174
1201
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1175
|
-
const fullPath =
|
|
1202
|
+
const fullPath = path10.join(projectRoot, filePath);
|
|
1176
1203
|
ctx.trackCreatedFile(fullPath);
|
|
1177
|
-
await
|
|
1178
|
-
await
|
|
1204
|
+
await fs9.ensureDir(path10.dirname(fullPath));
|
|
1205
|
+
await fs9.writeFile(fullPath, content);
|
|
1179
1206
|
count++;
|
|
1180
1207
|
}
|
|
1181
1208
|
await addUserRelation(projectRoot, "notifications Notification[]", ctx);
|
|
1209
|
+
await wireProtectedNav2(projectRoot, ctx);
|
|
1182
1210
|
await setConfigFlag3(projectRoot, ctx);
|
|
1183
1211
|
await ctx.commit();
|
|
1184
|
-
log.success(`Generated notifications feature
|
|
1212
|
+
log.success(`Generated and wired notifications feature (${count} files created)`);
|
|
1185
1213
|
log.blank();
|
|
1186
1214
|
log.step("prisma/schema/notification.prisma \u2014 Notification model");
|
|
1187
1215
|
log.step("src/features/notifications/ \u2014 types, validation, server logic, hooks, components");
|
|
1188
1216
|
log.step("src/app/api/protected/notifications/ \u2014 CRUD API routes");
|
|
1189
1217
|
log.blank();
|
|
1218
|
+
log.step("Wired automatically:");
|
|
1219
|
+
log.step(" \u2713 NotificationBell added to navigation bar");
|
|
1220
|
+
log.blank();
|
|
1190
1221
|
log.warn("Next steps:");
|
|
1191
1222
|
log.step("1. Run yarn db:push to sync the Prisma schema");
|
|
1192
|
-
log.step("2.
|
|
1193
|
-
log.step("3. Import createNotification from server to trigger notifications from other features:");
|
|
1223
|
+
log.step("2. Import createNotification from server to trigger notifications from other features:");
|
|
1194
1224
|
log.step(' import { createNotification } from "@/features/notifications/server"');
|
|
1195
1225
|
log.blank();
|
|
1196
1226
|
} catch (error) {
|
|
@@ -1198,13 +1228,30 @@ async function generateNotifications(projectRoot) {
|
|
|
1198
1228
|
throw error;
|
|
1199
1229
|
}
|
|
1200
1230
|
}
|
|
1231
|
+
async function wireProtectedNav2(projectRoot, ctx) {
|
|
1232
|
+
const navPath = path10.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
1233
|
+
if (!await fs9.pathExists(navPath)) return;
|
|
1234
|
+
await ctx.trackModifiedFile(navPath);
|
|
1235
|
+
let content = await fs9.readFile(navPath, "utf-8");
|
|
1236
|
+
if (content.includes("NotificationBell")) return;
|
|
1237
|
+
content = insertImportAfterDirectives(
|
|
1238
|
+
content,
|
|
1239
|
+
`import { NotificationBell } from '@/features/notifications';
|
|
1240
|
+
`
|
|
1241
|
+
);
|
|
1242
|
+
content = content.replace(
|
|
1243
|
+
/(<div className="flex items-center gap-3">)\s*\n(\s*)(<div className="hidden md:block">)/,
|
|
1244
|
+
"$1\n$2<NotificationBell />\n$2$3"
|
|
1245
|
+
);
|
|
1246
|
+
await fs9.writeFile(navPath, content);
|
|
1247
|
+
}
|
|
1201
1248
|
async function setConfigFlag3(projectRoot, ctx) {
|
|
1202
|
-
const configPath =
|
|
1203
|
-
if (!await
|
|
1249
|
+
const configPath = path10.join(projectRoot, "src", "config", "app.config.ts");
|
|
1250
|
+
if (!await fs9.pathExists(configPath)) return;
|
|
1204
1251
|
await ctx.trackModifiedFile(configPath);
|
|
1205
|
-
const content = await
|
|
1252
|
+
const content = await fs9.readFile(configPath, "utf-8");
|
|
1206
1253
|
const updated = content.replace(/notifications:\s*false/, "notifications: true");
|
|
1207
|
-
await
|
|
1254
|
+
await fs9.writeFile(configPath, updated);
|
|
1208
1255
|
}
|
|
1209
1256
|
function prismaSchema() {
|
|
1210
1257
|
return `// Generated by mars generate notifications (notifications@${GENERATOR_VERSION3})
|
|
@@ -1681,6 +1728,11 @@ export function NotificationPanel({
|
|
|
1681
1728
|
}
|
|
1682
1729
|
`;
|
|
1683
1730
|
}
|
|
1731
|
+
function featureIndex() {
|
|
1732
|
+
return `${STAMP3}
|
|
1733
|
+
export { NotificationBell, NotificationPanel } from './components';
|
|
1734
|
+
`;
|
|
1735
|
+
}
|
|
1684
1736
|
function componentIndex2() {
|
|
1685
1737
|
return `${STAMP3}
|
|
1686
1738
|
export { NotificationBell } from './NotificationBell';
|
|
@@ -1781,6 +1833,7 @@ var GENERATOR_VERSION3, STAMP3;
|
|
|
1781
1833
|
var init_notifications = __esm({
|
|
1782
1834
|
"src/generators/features/notifications.ts"() {
|
|
1783
1835
|
"use strict";
|
|
1836
|
+
init_client_file_patch();
|
|
1784
1837
|
init_logger();
|
|
1785
1838
|
init_rollback();
|
|
1786
1839
|
init_prisma();
|
|
@@ -1794,11 +1847,11 @@ var analytics_exports = {};
|
|
|
1794
1847
|
__export(analytics_exports, {
|
|
1795
1848
|
generateAnalytics: () => generateAnalytics
|
|
1796
1849
|
});
|
|
1797
|
-
import
|
|
1798
|
-
import
|
|
1850
|
+
import fs10 from "fs-extra";
|
|
1851
|
+
import path11 from "path";
|
|
1799
1852
|
async function generateAnalytics(projectRoot) {
|
|
1800
|
-
const featureDir =
|
|
1801
|
-
if (await
|
|
1853
|
+
const featureDir = path11.join(projectRoot, "src", "features", "analytics");
|
|
1854
|
+
if (await fs10.pathExists(featureDir)) {
|
|
1802
1855
|
log.error("Analytics feature already exists at src/features/analytics/");
|
|
1803
1856
|
return;
|
|
1804
1857
|
}
|
|
@@ -1813,21 +1866,25 @@ async function generateAnalytics(projectRoot) {
|
|
|
1813
1866
|
};
|
|
1814
1867
|
let count = 0;
|
|
1815
1868
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1816
|
-
const fullPath =
|
|
1869
|
+
const fullPath = path11.join(projectRoot, filePath);
|
|
1817
1870
|
ctx.trackCreatedFile(fullPath);
|
|
1818
|
-
await
|
|
1819
|
-
await
|
|
1871
|
+
await fs10.ensureDir(path11.dirname(fullPath));
|
|
1872
|
+
await fs10.writeFile(fullPath, content);
|
|
1820
1873
|
count++;
|
|
1821
1874
|
}
|
|
1875
|
+
await wireProviders2(projectRoot, ctx);
|
|
1822
1876
|
await setConfigFlag4(projectRoot, ctx);
|
|
1823
1877
|
await ctx.commit();
|
|
1824
|
-
log.success(`Generated analytics feature
|
|
1878
|
+
log.success(`Generated and wired analytics feature (${count} files created)`);
|
|
1825
1879
|
log.blank();
|
|
1826
1880
|
log.step("src/features/analytics/ \u2014 types and barrel exports");
|
|
1827
1881
|
log.step("src/lib/shared/components/providers/AnalyticsProvider.tsx \u2014 provider wrapper");
|
|
1828
1882
|
log.step("src/lib/shared/utils/analytics.ts \u2014 unified tracking API");
|
|
1829
1883
|
log.step("src/lib/shared/components/patterns/ConsentBanner.tsx \u2014 cookie consent banner");
|
|
1830
1884
|
log.blank();
|
|
1885
|
+
log.step("Wired automatically:");
|
|
1886
|
+
log.step(" \u2713 AnalyticsProvider wrapping app in providers.tsx");
|
|
1887
|
+
log.blank();
|
|
1831
1888
|
log.warn("Install dependencies for your chosen provider:");
|
|
1832
1889
|
log.step("Vercel: yarn add @vercel/analytics @vercel/speed-insights");
|
|
1833
1890
|
log.step("PostHog: yarn add posthog-js");
|
|
@@ -1836,20 +1893,46 @@ async function generateAnalytics(projectRoot) {
|
|
|
1836
1893
|
log.warn("Next steps:");
|
|
1837
1894
|
log.step("Set the provider in appConfig.services.analytics.provider");
|
|
1838
1895
|
log.step("Set required environment variables for the chosen provider");
|
|
1839
|
-
log.step("Add <AnalyticsProvider> and <ConsentBanner /> to your root layout");
|
|
1840
1896
|
log.blank();
|
|
1841
1897
|
} catch (error) {
|
|
1842
1898
|
await ctx.rollback();
|
|
1843
1899
|
throw error;
|
|
1844
1900
|
}
|
|
1845
1901
|
}
|
|
1902
|
+
async function wireProviders2(projectRoot, ctx) {
|
|
1903
|
+
const providersPath = path11.join(projectRoot, "src", "app", "providers.tsx");
|
|
1904
|
+
if (!await fs10.pathExists(providersPath)) return;
|
|
1905
|
+
await ctx.trackModifiedFile(providersPath);
|
|
1906
|
+
let content = await fs10.readFile(providersPath, "utf-8");
|
|
1907
|
+
if (content.includes("AnalyticsProvider")) return;
|
|
1908
|
+
content = insertImportAfterDirectives(
|
|
1909
|
+
content,
|
|
1910
|
+
`import { AnalyticsProvider } from '@/lib/shared/components/providers/AnalyticsProvider';
|
|
1911
|
+
`
|
|
1912
|
+
);
|
|
1913
|
+
content = content.replace(
|
|
1914
|
+
/return\s+(?:\(\s*)?((?:<\w+[^>]*>)[\s\S]*?\{children\}[\s\S]*?(?:<\/\w+>))\s*(?:\)\s*)?;/,
|
|
1915
|
+
(_match, jsx) => {
|
|
1916
|
+
return `return (
|
|
1917
|
+
<AnalyticsProvider>${jsx}</AnalyticsProvider>
|
|
1918
|
+
);`;
|
|
1919
|
+
}
|
|
1920
|
+
);
|
|
1921
|
+
await fs10.writeFile(providersPath, content);
|
|
1922
|
+
const written = await fs10.readFile(providersPath, "utf-8");
|
|
1923
|
+
if (!written.includes("AnalyticsProvider")) {
|
|
1924
|
+
throw new Error(
|
|
1925
|
+
"wireProviders: AnalyticsProvider was not inserted into providers.tsx \u2014 the return statement pattern did not match. Review the template file structure."
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1846
1929
|
async function setConfigFlag4(projectRoot, ctx) {
|
|
1847
|
-
const configPath =
|
|
1848
|
-
if (!await
|
|
1930
|
+
const configPath = path11.join(projectRoot, "src", "config", "app.config.ts");
|
|
1931
|
+
if (!await fs10.pathExists(configPath)) return;
|
|
1849
1932
|
await ctx.trackModifiedFile(configPath);
|
|
1850
|
-
const content = await
|
|
1933
|
+
const content = await fs10.readFile(configPath, "utf-8");
|
|
1851
1934
|
const updated = content.replace(/analytics:\s*false/, "analytics: true");
|
|
1852
|
-
await
|
|
1935
|
+
await fs10.writeFile(configPath, updated);
|
|
1853
1936
|
}
|
|
1854
1937
|
function types3() {
|
|
1855
1938
|
return `${STAMP4}
|
|
@@ -1872,7 +1955,7 @@ function analyticsProvider() {
|
|
|
1872
1955
|
return `${STAMP4}
|
|
1873
1956
|
'use client';
|
|
1874
1957
|
|
|
1875
|
-
import { useEffect, useState } from 'react';
|
|
1958
|
+
import { Suspense, useEffect, useState } from 'react';
|
|
1876
1959
|
import { usePathname, useSearchParams } from 'next/navigation';
|
|
1877
1960
|
import Script from 'next/script';
|
|
1878
1961
|
import { appConfig } from '@/config/app.config';
|
|
@@ -1895,7 +1978,9 @@ export function AnalyticsProvider({ children }: AnalyticsProviderProps) {
|
|
|
1895
1978
|
{provider === 'vercel' && <VercelAnalyticsProvider />}
|
|
1896
1979
|
{provider === 'posthog' && <PostHogAnalyticsProvider />}
|
|
1897
1980
|
{provider === 'google' && <GoogleAnalyticsProvider />}
|
|
1898
|
-
<
|
|
1981
|
+
<Suspense>
|
|
1982
|
+
<PageViewTracker />
|
|
1983
|
+
</Suspense>
|
|
1899
1984
|
{children}
|
|
1900
1985
|
</>
|
|
1901
1986
|
);
|
|
@@ -2059,22 +2144,18 @@ export function trackEvent(
|
|
|
2059
2144
|
|
|
2060
2145
|
switch (provider) {
|
|
2061
2146
|
case 'vercel': {
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
track(eventName, properties)
|
|
2065
|
-
|
|
2066
|
-
// @vercel/analytics not available
|
|
2067
|
-
}
|
|
2147
|
+
const vercelSpec = '@vercel/analytics';
|
|
2148
|
+
import(/* webpackIgnore: true */ vercelSpec)
|
|
2149
|
+
.then((mod) => mod.track(eventName, properties))
|
|
2150
|
+
.catch(() => {});
|
|
2068
2151
|
break;
|
|
2069
2152
|
}
|
|
2070
2153
|
|
|
2071
2154
|
case 'posthog': {
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
// posthog-js not available
|
|
2077
|
-
}
|
|
2155
|
+
const phSpec = 'posthog-js';
|
|
2156
|
+
import(/* webpackIgnore: true */ phSpec)
|
|
2157
|
+
.then((mod) => mod.default.capture(eventName, properties))
|
|
2158
|
+
.catch(() => {});
|
|
2078
2159
|
break;
|
|
2079
2160
|
}
|
|
2080
2161
|
|
|
@@ -2105,12 +2186,10 @@ export function identifyUser(
|
|
|
2105
2186
|
break;
|
|
2106
2187
|
|
|
2107
2188
|
case 'posthog': {
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
// posthog-js not available
|
|
2113
|
-
}
|
|
2189
|
+
const phSpec = 'posthog-js';
|
|
2190
|
+
import(/* webpackIgnore: true */ phSpec)
|
|
2191
|
+
.then((mod) => mod.default.identify(userId, traits))
|
|
2192
|
+
.catch(() => {});
|
|
2114
2193
|
break;
|
|
2115
2194
|
}
|
|
2116
2195
|
|
|
@@ -2199,6 +2278,7 @@ var GENERATOR_VERSION4, STAMP4;
|
|
|
2199
2278
|
var init_analytics = __esm({
|
|
2200
2279
|
"src/generators/features/analytics.ts"() {
|
|
2201
2280
|
"use strict";
|
|
2281
|
+
init_client_file_patch();
|
|
2202
2282
|
init_logger();
|
|
2203
2283
|
init_rollback();
|
|
2204
2284
|
GENERATOR_VERSION4 = "0.1.0";
|
|
@@ -2211,11 +2291,11 @@ var command_palette_exports = {};
|
|
|
2211
2291
|
__export(command_palette_exports, {
|
|
2212
2292
|
generateCommandPalette: () => generateCommandPalette
|
|
2213
2293
|
});
|
|
2214
|
-
import
|
|
2215
|
-
import
|
|
2294
|
+
import fs11 from "fs-extra";
|
|
2295
|
+
import path12 from "path";
|
|
2216
2296
|
async function generateCommandPalette(projectRoot) {
|
|
2217
|
-
const featureDir =
|
|
2218
|
-
if (await
|
|
2297
|
+
const featureDir = path12.join(projectRoot, "src", "features", "command-palette");
|
|
2298
|
+
if (await fs11.pathExists(featureDir)) {
|
|
2219
2299
|
log.error("Command palette feature already exists at src/features/command-palette/");
|
|
2220
2300
|
return;
|
|
2221
2301
|
}
|
|
@@ -2229,26 +2309,28 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2229
2309
|
"src/features/command-palette/components/CommandPalette.tsx": commandPalette(),
|
|
2230
2310
|
"src/features/command-palette/components/CommandTrigger.tsx": commandTrigger(),
|
|
2231
2311
|
"src/features/command-palette/components/index.ts": componentIndex3(),
|
|
2232
|
-
"src/features/command-palette/index.ts":
|
|
2312
|
+
"src/features/command-palette/index.ts": featureIndex2()
|
|
2233
2313
|
};
|
|
2234
2314
|
let count = 0;
|
|
2235
2315
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2236
|
-
const fullPath =
|
|
2316
|
+
const fullPath = path12.join(projectRoot, filePath);
|
|
2237
2317
|
ctx.trackCreatedFile(fullPath);
|
|
2238
|
-
await
|
|
2239
|
-
await
|
|
2318
|
+
await fs11.ensureDir(path12.dirname(fullPath));
|
|
2319
|
+
await fs11.writeFile(fullPath, content);
|
|
2240
2320
|
count++;
|
|
2241
2321
|
}
|
|
2242
2322
|
await addDependencies(projectRoot, { cmdk: "^1.0.0" });
|
|
2323
|
+
await wireLayout2(projectRoot, ctx);
|
|
2243
2324
|
await setConfigFlag5(projectRoot, ctx);
|
|
2244
2325
|
await ctx.commit();
|
|
2245
|
-
log.success(`Generated command palette feature
|
|
2326
|
+
log.success(`Generated and wired command palette feature (${count} files created)`);
|
|
2246
2327
|
log.blank();
|
|
2247
2328
|
log.step("src/features/command-palette/ \u2014 types, actions, registry, recent, components");
|
|
2248
2329
|
log.blank();
|
|
2249
|
-
log.
|
|
2250
|
-
log.step("
|
|
2251
|
-
log.step("
|
|
2330
|
+
log.step("Wired automatically:");
|
|
2331
|
+
log.step(" \u2713 CommandPalette overlay mounted in root layout");
|
|
2332
|
+
log.step(" \u2713 Press \u2318K / Ctrl+K to open");
|
|
2333
|
+
log.blank();
|
|
2252
2334
|
log.step("Register custom actions with registerAction() from any feature");
|
|
2253
2335
|
log.blank();
|
|
2254
2336
|
} catch (error) {
|
|
@@ -2256,13 +2338,27 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2256
2338
|
throw error;
|
|
2257
2339
|
}
|
|
2258
2340
|
}
|
|
2341
|
+
async function wireLayout2(projectRoot, ctx) {
|
|
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");
|
|
2345
|
+
if (content.includes("CommandPalette")) return;
|
|
2346
|
+
await ctx.trackModifiedFile(layoutPath);
|
|
2347
|
+
content = `import { CommandPalette } from '@/features/command-palette';
|
|
2348
|
+
${content}`;
|
|
2349
|
+
content = content.replace(
|
|
2350
|
+
/(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
|
|
2351
|
+
"$1$2\n$1<CommandPalette />"
|
|
2352
|
+
);
|
|
2353
|
+
await fs11.writeFile(layoutPath, content);
|
|
2354
|
+
}
|
|
2259
2355
|
async function setConfigFlag5(projectRoot, ctx) {
|
|
2260
|
-
const configPath =
|
|
2261
|
-
if (!await
|
|
2356
|
+
const configPath = path12.join(projectRoot, "src", "config", "app.config.ts");
|
|
2357
|
+
if (!await fs11.pathExists(configPath)) return;
|
|
2262
2358
|
await ctx.trackModifiedFile(configPath);
|
|
2263
|
-
const content = await
|
|
2359
|
+
const content = await fs11.readFile(configPath, "utf-8");
|
|
2264
2360
|
const updated = content.replace(/commandPalette:\s*false/, "commandPalette: true");
|
|
2265
|
-
await
|
|
2361
|
+
await fs11.writeFile(configPath, updated);
|
|
2266
2362
|
}
|
|
2267
2363
|
function types4() {
|
|
2268
2364
|
return `${STAMP5}
|
|
@@ -2652,7 +2748,7 @@ export { CommandPalette } from './CommandPalette';
|
|
|
2652
2748
|
export { CommandTrigger } from './CommandTrigger';
|
|
2653
2749
|
`;
|
|
2654
2750
|
}
|
|
2655
|
-
function
|
|
2751
|
+
function featureIndex2() {
|
|
2656
2752
|
return `${STAMP5}
|
|
2657
2753
|
export { CommandPalette, CommandTrigger } from './components';
|
|
2658
2754
|
export { registerAction, getCustomActions } from './registry';
|
|
@@ -2678,11 +2774,11 @@ var onboarding_exports = {};
|
|
|
2678
2774
|
__export(onboarding_exports, {
|
|
2679
2775
|
generateOnboarding: () => generateOnboarding
|
|
2680
2776
|
});
|
|
2681
|
-
import
|
|
2682
|
-
import
|
|
2777
|
+
import fs12 from "fs-extra";
|
|
2778
|
+
import path13 from "path";
|
|
2683
2779
|
async function generateOnboarding(projectRoot) {
|
|
2684
|
-
const featureDir =
|
|
2685
|
-
if (await
|
|
2780
|
+
const featureDir = path13.join(projectRoot, "src", "features", "onboarding");
|
|
2781
|
+
if (await fs12.pathExists(featureDir)) {
|
|
2686
2782
|
log.error("Onboarding feature already exists at src/features/onboarding/");
|
|
2687
2783
|
return;
|
|
2688
2784
|
}
|
|
@@ -2700,17 +2796,18 @@ async function generateOnboarding(projectRoot) {
|
|
|
2700
2796
|
"src/app/(protected)/onboarding/page.tsx": onboardingPage(),
|
|
2701
2797
|
"src/app/api/protected/onboarding/route.ts": getProgressRoute(),
|
|
2702
2798
|
"src/app/api/protected/onboarding/step/route.ts": stepActionRoute(),
|
|
2703
|
-
"src/features/onboarding/index.ts":
|
|
2799
|
+
"src/features/onboarding/index.ts": featureIndex3()
|
|
2704
2800
|
};
|
|
2705
2801
|
let count = 0;
|
|
2706
2802
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2707
|
-
const fullPath =
|
|
2803
|
+
const fullPath = path13.join(projectRoot, filePath);
|
|
2708
2804
|
ctx.trackCreatedFile(fullPath);
|
|
2709
|
-
await
|
|
2710
|
-
await
|
|
2805
|
+
await fs12.ensureDir(path13.dirname(fullPath));
|
|
2806
|
+
await fs12.writeFile(fullPath, content);
|
|
2711
2807
|
count++;
|
|
2712
2808
|
}
|
|
2713
2809
|
await addUserRelation(projectRoot, "onboardingProgress OnboardingProgress?", ctx);
|
|
2810
|
+
await registerRoute(projectRoot, "onboarding", "/onboarding", ctx);
|
|
2714
2811
|
await setConfigFlag6(projectRoot, ctx);
|
|
2715
2812
|
await ctx.commit();
|
|
2716
2813
|
log.success(`Generated onboarding feature with ${count} files`);
|
|
@@ -2731,12 +2828,12 @@ async function generateOnboarding(projectRoot) {
|
|
|
2731
2828
|
}
|
|
2732
2829
|
}
|
|
2733
2830
|
async function setConfigFlag6(projectRoot, ctx) {
|
|
2734
|
-
const configPath =
|
|
2735
|
-
if (!await
|
|
2831
|
+
const configPath = path13.join(projectRoot, "src", "config", "app.config.ts");
|
|
2832
|
+
if (!await fs12.pathExists(configPath)) return;
|
|
2736
2833
|
await ctx.trackModifiedFile(configPath);
|
|
2737
|
-
const content = await
|
|
2834
|
+
const content = await fs12.readFile(configPath, "utf-8");
|
|
2738
2835
|
const updated = content.replace(/onboarding:\s*false/, "onboarding: true");
|
|
2739
|
-
await
|
|
2836
|
+
await fs12.writeFile(configPath, updated);
|
|
2740
2837
|
}
|
|
2741
2838
|
function prismaSchema2() {
|
|
2742
2839
|
return `// Generated by mars generate onboarding (onboarding@${GENERATOR_VERSION6})
|
|
@@ -3231,7 +3328,7 @@ export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
|
|
|
3231
3328
|
});
|
|
3232
3329
|
`;
|
|
3233
3330
|
}
|
|
3234
|
-
function
|
|
3331
|
+
function featureIndex3() {
|
|
3235
3332
|
return `${STAMP6}
|
|
3236
3333
|
export { ONBOARDING_STEPS } from './config';
|
|
3237
3334
|
export type { OnboardingStep } from './config';
|
|
@@ -3252,6 +3349,7 @@ var init_onboarding = __esm({
|
|
|
3252
3349
|
init_logger();
|
|
3253
3350
|
init_rollback();
|
|
3254
3351
|
init_prisma();
|
|
3352
|
+
init_routes();
|
|
3255
3353
|
GENERATOR_VERSION6 = "0.1.0";
|
|
3256
3354
|
STAMP6 = `// @mars-generated onboarding@${GENERATOR_VERSION6}`;
|
|
3257
3355
|
}
|
|
@@ -3262,11 +3360,11 @@ var search_exports = {};
|
|
|
3262
3360
|
__export(search_exports, {
|
|
3263
3361
|
generateSearch: () => generateSearch
|
|
3264
3362
|
});
|
|
3265
|
-
import
|
|
3266
|
-
import
|
|
3363
|
+
import fs13 from "fs-extra";
|
|
3364
|
+
import path14 from "path";
|
|
3267
3365
|
async function generateSearch(projectRoot) {
|
|
3268
|
-
const featureDir =
|
|
3269
|
-
if (await
|
|
3366
|
+
const featureDir = path14.join(projectRoot, "src", "features", "search");
|
|
3367
|
+
if (await fs13.pathExists(featureDir)) {
|
|
3270
3368
|
log.error("Search feature already exists at src/features/search/");
|
|
3271
3369
|
return;
|
|
3272
3370
|
}
|
|
@@ -3279,25 +3377,30 @@ async function generateSearch(projectRoot) {
|
|
|
3279
3377
|
"src/features/search/server/index.ts": serverIndex(),
|
|
3280
3378
|
"src/features/search/hooks/use-search.ts": useSearchHook(),
|
|
3281
3379
|
"src/features/search/components/SearchInput.tsx": searchInput(),
|
|
3380
|
+
"src/features/search/components/NavSearch.tsx": navSearch(),
|
|
3282
3381
|
"src/features/search/components/index.ts": componentIndex5(),
|
|
3283
|
-
"src/features/search/index.ts":
|
|
3382
|
+
"src/features/search/index.ts": featureIndex4(),
|
|
3284
3383
|
"src/app/api/protected/search/route.ts": searchRoute()
|
|
3285
3384
|
};
|
|
3286
3385
|
let count = 0;
|
|
3287
3386
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3288
|
-
const fullPath =
|
|
3387
|
+
const fullPath = path14.join(projectRoot, filePath);
|
|
3289
3388
|
ctx.trackCreatedFile(fullPath);
|
|
3290
|
-
await
|
|
3291
|
-
await
|
|
3389
|
+
await fs13.ensureDir(path14.dirname(fullPath));
|
|
3390
|
+
await fs13.writeFile(fullPath, content);
|
|
3292
3391
|
count++;
|
|
3293
3392
|
}
|
|
3393
|
+
await wireProtectedNav3(projectRoot, ctx);
|
|
3294
3394
|
await setConfigFlag7(projectRoot, ctx);
|
|
3295
3395
|
await ctx.commit();
|
|
3296
|
-
log.success(`Generated search feature
|
|
3396
|
+
log.success(`Generated and wired search feature (${count} files created)`);
|
|
3297
3397
|
log.blank();
|
|
3298
3398
|
log.step("src/features/search/ \u2014 types, validation, server logic, hooks, components");
|
|
3299
3399
|
log.step("src/app/api/protected/search/ \u2014 authenticated search endpoint");
|
|
3300
3400
|
log.blank();
|
|
3401
|
+
log.step("Wired automatically:");
|
|
3402
|
+
log.step(" \u2713 NavSearch added to navigation bar");
|
|
3403
|
+
log.blank();
|
|
3301
3404
|
log.warn("Search uses Postgres full-text search by default (no extra setup).");
|
|
3302
3405
|
log.blank();
|
|
3303
3406
|
log.step("For Algolia: yarn add algoliasearch + set ALGOLIA_APP_ID, ALGOLIA_API_KEY env vars");
|
|
@@ -3311,13 +3414,30 @@ async function generateSearch(projectRoot) {
|
|
|
3311
3414
|
throw error;
|
|
3312
3415
|
}
|
|
3313
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
|
+
}
|
|
3314
3434
|
async function setConfigFlag7(projectRoot, ctx) {
|
|
3315
|
-
const configPath =
|
|
3316
|
-
if (!await
|
|
3435
|
+
const configPath = path14.join(projectRoot, "src", "config", "app.config.ts");
|
|
3436
|
+
if (!await fs13.pathExists(configPath)) return;
|
|
3317
3437
|
await ctx.trackModifiedFile(configPath);
|
|
3318
|
-
const content = await
|
|
3438
|
+
const content = await fs13.readFile(configPath, "utf-8");
|
|
3319
3439
|
const updated = content.replace(/search:\s*false/, "search: true");
|
|
3320
|
-
await
|
|
3440
|
+
await fs13.writeFile(configPath, updated);
|
|
3321
3441
|
}
|
|
3322
3442
|
function types6() {
|
|
3323
3443
|
return `${STAMP7}
|
|
@@ -3587,17 +3707,39 @@ export function SearchInput({
|
|
|
3587
3707
|
}
|
|
3588
3708
|
`;
|
|
3589
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
|
+
}
|
|
3590
3731
|
function componentIndex5() {
|
|
3591
3732
|
return `${STAMP7}
|
|
3592
3733
|
export { SearchInput } from './SearchInput';
|
|
3734
|
+
export { NavSearch } from './NavSearch';
|
|
3593
3735
|
`;
|
|
3594
3736
|
}
|
|
3595
|
-
function
|
|
3737
|
+
function featureIndex4() {
|
|
3596
3738
|
return `${STAMP7}
|
|
3597
3739
|
export type { SearchResult, SearchOptions, SearchProvider } from './types';
|
|
3598
3740
|
export { searchParamsSchema } from './validation/schemas';
|
|
3599
3741
|
export type { SearchParams } from './validation/schemas';
|
|
3600
|
-
export { SearchInput } from './components';
|
|
3742
|
+
export { SearchInput, NavSearch } from './components';
|
|
3601
3743
|
export { useSearch } from './hooks/use-search';
|
|
3602
3744
|
`;
|
|
3603
3745
|
}
|
|
@@ -3636,6 +3778,7 @@ var GENERATOR_VERSION7, STAMP7;
|
|
|
3636
3778
|
var init_search = __esm({
|
|
3637
3779
|
"src/generators/features/search.ts"() {
|
|
3638
3780
|
"use strict";
|
|
3781
|
+
init_client_file_patch();
|
|
3639
3782
|
init_logger();
|
|
3640
3783
|
init_rollback();
|
|
3641
3784
|
GENERATOR_VERSION7 = "0.1.0";
|
|
@@ -3648,11 +3791,11 @@ var realtime_exports = {};
|
|
|
3648
3791
|
__export(realtime_exports, {
|
|
3649
3792
|
generateRealtime: () => generateRealtime
|
|
3650
3793
|
});
|
|
3651
|
-
import
|
|
3652
|
-
import
|
|
3794
|
+
import fs14 from "fs-extra";
|
|
3795
|
+
import path15 from "path";
|
|
3653
3796
|
async function generateRealtime(projectRoot) {
|
|
3654
|
-
const featureDir =
|
|
3655
|
-
if (await
|
|
3797
|
+
const featureDir = path15.join(projectRoot, "src", "features", "realtime");
|
|
3798
|
+
if (await fs14.pathExists(featureDir)) {
|
|
3656
3799
|
log.error("Realtime feature already exists at src/features/realtime/");
|
|
3657
3800
|
return;
|
|
3658
3801
|
}
|
|
@@ -3664,15 +3807,15 @@ async function generateRealtime(projectRoot) {
|
|
|
3664
3807
|
"src/features/realtime/server/sse.ts": sseProvider(),
|
|
3665
3808
|
"src/features/realtime/hooks/use-event-source.ts": useEventSourceHook(),
|
|
3666
3809
|
"src/features/realtime/hooks/index.ts": hooksIndex(),
|
|
3667
|
-
"src/features/realtime/index.ts":
|
|
3810
|
+
"src/features/realtime/index.ts": featureIndex5(),
|
|
3668
3811
|
"src/app/api/protected/realtime/stream/route.ts": sseStreamRoute()
|
|
3669
3812
|
};
|
|
3670
3813
|
let count = 0;
|
|
3671
3814
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3672
|
-
const fullPath =
|
|
3815
|
+
const fullPath = path15.join(projectRoot, filePath);
|
|
3673
3816
|
ctx.trackCreatedFile(fullPath);
|
|
3674
|
-
await
|
|
3675
|
-
await
|
|
3817
|
+
await fs14.ensureDir(path15.dirname(fullPath));
|
|
3818
|
+
await fs14.writeFile(fullPath, content);
|
|
3676
3819
|
count++;
|
|
3677
3820
|
}
|
|
3678
3821
|
await setConfigFlag8(projectRoot, ctx);
|
|
@@ -3697,12 +3840,12 @@ async function generateRealtime(projectRoot) {
|
|
|
3697
3840
|
}
|
|
3698
3841
|
}
|
|
3699
3842
|
async function setConfigFlag8(projectRoot, ctx) {
|
|
3700
|
-
const configPath =
|
|
3701
|
-
if (!await
|
|
3843
|
+
const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
|
|
3844
|
+
if (!await fs14.pathExists(configPath)) return;
|
|
3702
3845
|
await ctx.trackModifiedFile(configPath);
|
|
3703
|
-
const content = await
|
|
3846
|
+
const content = await fs14.readFile(configPath, "utf-8");
|
|
3704
3847
|
const updated = content.replace(/realtime:\s*false/, "realtime: true");
|
|
3705
|
-
await
|
|
3848
|
+
await fs14.writeFile(configPath, updated);
|
|
3706
3849
|
}
|
|
3707
3850
|
function types7() {
|
|
3708
3851
|
return `${STAMP8}
|
|
@@ -3912,7 +4055,7 @@ function hooksIndex() {
|
|
|
3912
4055
|
export { useEventSource } from './use-event-source';
|
|
3913
4056
|
`;
|
|
3914
4057
|
}
|
|
3915
|
-
function
|
|
4058
|
+
function featureIndex5() {
|
|
3916
4059
|
return `${STAMP8}
|
|
3917
4060
|
export type {
|
|
3918
4061
|
RealtimeProvider,
|
|
@@ -4011,11 +4154,11 @@ var ai_exports = {};
|
|
|
4011
4154
|
__export(ai_exports, {
|
|
4012
4155
|
generateAI: () => generateAI
|
|
4013
4156
|
});
|
|
4014
|
-
import
|
|
4015
|
-
import
|
|
4157
|
+
import fs15 from "fs-extra";
|
|
4158
|
+
import path16 from "path";
|
|
4016
4159
|
async function generateAI(projectRoot) {
|
|
4017
|
-
const featureDir =
|
|
4018
|
-
if (await
|
|
4160
|
+
const featureDir = path16.join(projectRoot, "src", "features", "ai");
|
|
4161
|
+
if (await fs15.pathExists(featureDir)) {
|
|
4019
4162
|
log.error("AI feature already exists at src/features/ai/");
|
|
4020
4163
|
return;
|
|
4021
4164
|
}
|
|
@@ -4033,10 +4176,10 @@ async function generateAI(projectRoot) {
|
|
|
4033
4176
|
};
|
|
4034
4177
|
let count = 0;
|
|
4035
4178
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4036
|
-
const fullPath =
|
|
4179
|
+
const fullPath = path16.join(projectRoot, filePath);
|
|
4037
4180
|
ctx.trackCreatedFile(fullPath);
|
|
4038
|
-
await
|
|
4039
|
-
await
|
|
4181
|
+
await fs15.ensureDir(path16.dirname(fullPath));
|
|
4182
|
+
await fs15.writeFile(fullPath, content);
|
|
4040
4183
|
count++;
|
|
4041
4184
|
}
|
|
4042
4185
|
await addDependencies(projectRoot, {
|
|
@@ -4062,12 +4205,12 @@ async function generateAI(projectRoot) {
|
|
|
4062
4205
|
}
|
|
4063
4206
|
}
|
|
4064
4207
|
async function setConfigFlag9(projectRoot, ctx) {
|
|
4065
|
-
const configPath =
|
|
4066
|
-
if (!await
|
|
4208
|
+
const configPath = path16.join(projectRoot, "src", "config", "app.config.ts");
|
|
4209
|
+
if (!await fs15.pathExists(configPath)) return;
|
|
4067
4210
|
await ctx.trackModifiedFile(configPath);
|
|
4068
|
-
const content = await
|
|
4211
|
+
const content = await fs15.readFile(configPath, "utf-8");
|
|
4069
4212
|
const updated = content.replace(/ai:\s*false/, "ai: true");
|
|
4070
|
-
await
|
|
4213
|
+
await fs15.writeFile(configPath, updated);
|
|
4071
4214
|
}
|
|
4072
4215
|
function types8() {
|
|
4073
4216
|
return `${STAMP9}
|
|
@@ -4506,11 +4649,11 @@ var cookie_consent_exports = {};
|
|
|
4506
4649
|
__export(cookie_consent_exports, {
|
|
4507
4650
|
generateCookieConsent: () => generateCookieConsent
|
|
4508
4651
|
});
|
|
4509
|
-
import
|
|
4510
|
-
import
|
|
4652
|
+
import fs16 from "fs-extra";
|
|
4653
|
+
import path17 from "path";
|
|
4511
4654
|
async function generateCookieConsent(projectRoot) {
|
|
4512
|
-
const featureDir =
|
|
4513
|
-
if (await
|
|
4655
|
+
const featureDir = path17.join(projectRoot, "src", "features", "cookie-consent");
|
|
4656
|
+
if (await fs16.pathExists(featureDir)) {
|
|
4514
4657
|
log.error("Cookie consent feature already exists at src/features/cookie-consent/");
|
|
4515
4658
|
return;
|
|
4516
4659
|
}
|
|
@@ -4522,27 +4665,29 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4522
4665
|
"src/features/cookie-consent/components/CookieConsentBanner.tsx": cookieConsentBanner(),
|
|
4523
4666
|
"src/features/cookie-consent/components/CookiePreferencesDialog.tsx": cookiePreferencesDialog(),
|
|
4524
4667
|
"src/features/cookie-consent/components/index.ts": componentsIndex(),
|
|
4525
|
-
"src/features/cookie-consent/index.ts":
|
|
4668
|
+
"src/features/cookie-consent/index.ts": featureIndex6()
|
|
4526
4669
|
};
|
|
4527
4670
|
let count = 0;
|
|
4528
4671
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4529
|
-
const fullPath =
|
|
4672
|
+
const fullPath = path17.join(projectRoot, filePath);
|
|
4530
4673
|
ctx.trackCreatedFile(fullPath);
|
|
4531
|
-
await
|
|
4532
|
-
await
|
|
4674
|
+
await fs16.ensureDir(path17.dirname(fullPath));
|
|
4675
|
+
await fs16.writeFile(fullPath, content);
|
|
4533
4676
|
count++;
|
|
4534
4677
|
}
|
|
4678
|
+
await wireLayout3(projectRoot, ctx);
|
|
4535
4679
|
await setConfigFlag10(projectRoot, ctx);
|
|
4536
4680
|
await ctx.commit();
|
|
4537
|
-
log.success(`Generated cookie consent feature
|
|
4681
|
+
log.success(`Generated and wired cookie consent feature (${count} files created)`);
|
|
4538
4682
|
log.blank();
|
|
4539
4683
|
log.step("src/features/cookie-consent/types.ts \u2014 ConsentStatus, ConsentPreferences");
|
|
4540
4684
|
log.step("src/features/cookie-consent/hooks/use-consent.ts \u2014 useConsent hook");
|
|
4541
4685
|
log.step("src/features/cookie-consent/components/CookieConsentBanner.tsx \u2014 banner");
|
|
4542
4686
|
log.step("src/features/cookie-consent/components/CookiePreferencesDialog.tsx \u2014 preferences modal");
|
|
4543
4687
|
log.blank();
|
|
4544
|
-
log.
|
|
4545
|
-
log.step("
|
|
4688
|
+
log.step("Wired automatically:");
|
|
4689
|
+
log.step(" \u2713 CookieConsentBanner mounted in root layout");
|
|
4690
|
+
log.blank();
|
|
4546
4691
|
log.step("Optionally add <CookiePreferencesDialog /> for granular consent management");
|
|
4547
4692
|
log.step("Check consent status with useConsent() before loading tracking scripts");
|
|
4548
4693
|
log.blank();
|
|
@@ -4551,13 +4696,27 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4551
4696
|
throw error;
|
|
4552
4697
|
}
|
|
4553
4698
|
}
|
|
4699
|
+
async function wireLayout3(projectRoot, ctx) {
|
|
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");
|
|
4703
|
+
if (content.includes("CookieConsentBanner")) return;
|
|
4704
|
+
await ctx.trackModifiedFile(layoutPath);
|
|
4705
|
+
content = `import { CookieConsentBanner } from '@/features/cookie-consent';
|
|
4706
|
+
${content}`;
|
|
4707
|
+
content = content.replace(
|
|
4708
|
+
/(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
|
|
4709
|
+
"$1$2\n$1<CookieConsentBanner />"
|
|
4710
|
+
);
|
|
4711
|
+
await fs16.writeFile(layoutPath, content);
|
|
4712
|
+
}
|
|
4554
4713
|
async function setConfigFlag10(projectRoot, ctx) {
|
|
4555
|
-
const configPath =
|
|
4556
|
-
if (!await
|
|
4714
|
+
const configPath = path17.join(projectRoot, "src", "config", "app.config.ts");
|
|
4715
|
+
if (!await fs16.pathExists(configPath)) return;
|
|
4557
4716
|
await ctx.trackModifiedFile(configPath);
|
|
4558
|
-
const content = await
|
|
4717
|
+
const content = await fs16.readFile(configPath, "utf-8");
|
|
4559
4718
|
const updated = content.replace(/cookieConsent:\s*false/, "cookieConsent: true");
|
|
4560
|
-
await
|
|
4719
|
+
await fs16.writeFile(configPath, updated);
|
|
4561
4720
|
}
|
|
4562
4721
|
function types9() {
|
|
4563
4722
|
return `${STAMP10}
|
|
@@ -4819,7 +4978,7 @@ export { CookieConsentBanner } from './CookieConsentBanner';
|
|
|
4819
4978
|
export { CookiePreferencesDialog } from './CookiePreferencesDialog';
|
|
4820
4979
|
`;
|
|
4821
4980
|
}
|
|
4822
|
-
function
|
|
4981
|
+
function featureIndex6() {
|
|
4823
4982
|
return `${STAMP10}
|
|
4824
4983
|
export type { ConsentStatus, ConsentPreferences } from './types';
|
|
4825
4984
|
export { useConsent } from './hooks/use-consent';
|
|
@@ -4842,11 +5001,11 @@ var coming_soon_exports = {};
|
|
|
4842
5001
|
__export(coming_soon_exports, {
|
|
4843
5002
|
generateComingSoon: () => generateComingSoon
|
|
4844
5003
|
});
|
|
4845
|
-
import
|
|
4846
|
-
import
|
|
5004
|
+
import fs17 from "fs-extra";
|
|
5005
|
+
import path18 from "path";
|
|
4847
5006
|
async function generateComingSoon(projectRoot) {
|
|
4848
|
-
const featureDir =
|
|
4849
|
-
if (await
|
|
5007
|
+
const featureDir = path18.join(projectRoot, "src", "features", "coming-soon");
|
|
5008
|
+
if (await fs17.pathExists(featureDir)) {
|
|
4850
5009
|
log.error("Coming soon feature already exists at src/features/coming-soon/");
|
|
4851
5010
|
return;
|
|
4852
5011
|
}
|
|
@@ -4863,12 +5022,13 @@ async function generateComingSoon(projectRoot) {
|
|
|
4863
5022
|
};
|
|
4864
5023
|
let count = 0;
|
|
4865
5024
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4866
|
-
const fullPath =
|
|
5025
|
+
const fullPath = path18.join(projectRoot, filePath);
|
|
4867
5026
|
ctx.trackCreatedFile(fullPath);
|
|
4868
|
-
await
|
|
4869
|
-
await
|
|
5027
|
+
await fs17.ensureDir(path18.dirname(fullPath));
|
|
5028
|
+
await fs17.writeFile(fullPath, content);
|
|
4870
5029
|
count++;
|
|
4871
5030
|
}
|
|
5031
|
+
await registerRoute(projectRoot, "comingSoon", "/coming-soon", ctx);
|
|
4872
5032
|
await setConfigFlag11(projectRoot, ctx);
|
|
4873
5033
|
await ctx.commit();
|
|
4874
5034
|
log.success(`Generated coming soon feature with ${count} files`);
|
|
@@ -4887,12 +5047,12 @@ async function generateComingSoon(projectRoot) {
|
|
|
4887
5047
|
}
|
|
4888
5048
|
}
|
|
4889
5049
|
async function setConfigFlag11(projectRoot, ctx) {
|
|
4890
|
-
const configPath =
|
|
4891
|
-
if (!await
|
|
5050
|
+
const configPath = path18.join(projectRoot, "src", "config", "app.config.ts");
|
|
5051
|
+
if (!await fs17.pathExists(configPath)) return;
|
|
4892
5052
|
await ctx.trackModifiedFile(configPath);
|
|
4893
|
-
const content = await
|
|
5053
|
+
const content = await fs17.readFile(configPath, "utf-8");
|
|
4894
5054
|
const updated = content.replace(/comingSoon:\s*false/, "comingSoon: true");
|
|
4895
|
-
await
|
|
5055
|
+
await fs17.writeFile(configPath, updated);
|
|
4896
5056
|
}
|
|
4897
5057
|
function types10() {
|
|
4898
5058
|
return `${STAMP11}
|
|
@@ -5069,6 +5229,7 @@ var init_coming_soon = __esm({
|
|
|
5069
5229
|
"use strict";
|
|
5070
5230
|
init_logger();
|
|
5071
5231
|
init_rollback();
|
|
5232
|
+
init_routes();
|
|
5072
5233
|
GENERATOR_VERSION11 = "0.1.0";
|
|
5073
5234
|
STAMP11 = `// @mars-generated coming-soon@${GENERATOR_VERSION11}`;
|
|
5074
5235
|
}
|
|
@@ -5079,11 +5240,11 @@ var sentry_exports = {};
|
|
|
5079
5240
|
__export(sentry_exports, {
|
|
5080
5241
|
generateSentry: () => generateSentry
|
|
5081
5242
|
});
|
|
5082
|
-
import
|
|
5083
|
-
import
|
|
5243
|
+
import fs18 from "fs-extra";
|
|
5244
|
+
import path19 from "path";
|
|
5084
5245
|
async function generateSentry(projectRoot) {
|
|
5085
|
-
const featureDir =
|
|
5086
|
-
if (await
|
|
5246
|
+
const featureDir = path19.join(projectRoot, "src", "features", "sentry");
|
|
5247
|
+
if (await fs18.pathExists(featureDir)) {
|
|
5087
5248
|
log.error("Sentry feature already exists at src/features/sentry/");
|
|
5088
5249
|
return;
|
|
5089
5250
|
}
|
|
@@ -5099,24 +5260,27 @@ async function generateSentry(projectRoot) {
|
|
|
5099
5260
|
};
|
|
5100
5261
|
let count = 0;
|
|
5101
5262
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5102
|
-
const fullPath =
|
|
5263
|
+
const fullPath = path19.join(projectRoot, filePath);
|
|
5103
5264
|
ctx.trackCreatedFile(fullPath);
|
|
5104
|
-
await
|
|
5105
|
-
await
|
|
5265
|
+
await fs18.ensureDir(path19.dirname(fullPath));
|
|
5266
|
+
await fs18.writeFile(fullPath, content);
|
|
5106
5267
|
count++;
|
|
5107
5268
|
}
|
|
5108
5269
|
await addDependencies(projectRoot, { "@sentry/nextjs": "^8.0.0" });
|
|
5270
|
+
await wireRootLayout(projectRoot, ctx);
|
|
5109
5271
|
await setConfigFlag12(projectRoot, ctx);
|
|
5110
5272
|
await ctx.commit();
|
|
5111
|
-
log.success(`Generated sentry feature
|
|
5273
|
+
log.success(`Generated and wired sentry feature (${count} files created)`);
|
|
5112
5274
|
log.blank();
|
|
5113
5275
|
log.step("src/features/sentry/ \u2014 types, client-init, server-init, ErrorBoundary");
|
|
5114
5276
|
log.blank();
|
|
5277
|
+
log.step("Wired automatically:");
|
|
5278
|
+
log.step(" \u2713 ErrorBoundary wrapping app content in root layout");
|
|
5279
|
+
log.blank();
|
|
5115
5280
|
log.warn("Optional: run npx @sentry/wizard@latest -i nextjs for full setup");
|
|
5116
5281
|
log.blank();
|
|
5117
5282
|
log.warn("Next steps:");
|
|
5118
5283
|
log.step("Set NEXT_PUBLIC_SENTRY_DSN and SENTRY_DSN environment variables");
|
|
5119
|
-
log.step("Wrap pages or layouts with <ErrorBoundary> for graceful error handling");
|
|
5120
5284
|
log.step("Import client-init and server-init in your instrumentation files");
|
|
5121
5285
|
log.blank();
|
|
5122
5286
|
} catch (error) {
|
|
@@ -5124,13 +5288,27 @@ async function generateSentry(projectRoot) {
|
|
|
5124
5288
|
throw error;
|
|
5125
5289
|
}
|
|
5126
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
|
+
}
|
|
5127
5305
|
async function setConfigFlag12(projectRoot, ctx) {
|
|
5128
|
-
const configPath =
|
|
5129
|
-
if (!await
|
|
5306
|
+
const configPath = path19.join(projectRoot, "src", "config", "app.config.ts");
|
|
5307
|
+
if (!await fs18.pathExists(configPath)) return;
|
|
5130
5308
|
await ctx.trackModifiedFile(configPath);
|
|
5131
|
-
const content = await
|
|
5309
|
+
const content = await fs18.readFile(configPath, "utf-8");
|
|
5132
5310
|
const updated = content.replace(/sentry:\s*false/, "sentry: true");
|
|
5133
|
-
await
|
|
5311
|
+
await fs18.writeFile(configPath, updated);
|
|
5134
5312
|
}
|
|
5135
5313
|
function types11() {
|
|
5136
5314
|
return `${STAMP12}
|
|
@@ -5275,11 +5453,11 @@ var feature_flags_exports = {};
|
|
|
5275
5453
|
__export(feature_flags_exports, {
|
|
5276
5454
|
generateFeatureFlags: () => generateFeatureFlags
|
|
5277
5455
|
});
|
|
5278
|
-
import
|
|
5279
|
-
import
|
|
5456
|
+
import fs19 from "fs-extra";
|
|
5457
|
+
import path20 from "path";
|
|
5280
5458
|
async function generateFeatureFlags(projectRoot) {
|
|
5281
|
-
const featureDir =
|
|
5282
|
-
if (await
|
|
5459
|
+
const featureDir = path20.join(projectRoot, "src", "features", "feature-flags");
|
|
5460
|
+
if (await fs19.pathExists(featureDir)) {
|
|
5283
5461
|
log.error("Feature flags feature already exists at src/features/feature-flags/");
|
|
5284
5462
|
return;
|
|
5285
5463
|
}
|
|
@@ -5292,16 +5470,16 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5292
5470
|
"src/features/feature-flags/hooks/use-feature-flag.ts": useFeatureFlag(),
|
|
5293
5471
|
"src/features/feature-flags/components/FeatureGate.tsx": featureGate(),
|
|
5294
5472
|
"src/features/feature-flags/components/index.ts": componentsIndex3(),
|
|
5295
|
-
"src/features/feature-flags/index.ts":
|
|
5473
|
+
"src/features/feature-flags/index.ts": featureIndex7(),
|
|
5296
5474
|
"src/config/feature-flags.json": featureFlagsJson(),
|
|
5297
5475
|
"src/app/api/protected/feature-flags/route.ts": apiRoute()
|
|
5298
5476
|
};
|
|
5299
5477
|
let count = 0;
|
|
5300
5478
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5301
|
-
const fullPath =
|
|
5479
|
+
const fullPath = path20.join(projectRoot, filePath);
|
|
5302
5480
|
ctx.trackCreatedFile(fullPath);
|
|
5303
|
-
await
|
|
5304
|
-
await
|
|
5481
|
+
await fs19.ensureDir(path20.dirname(fullPath));
|
|
5482
|
+
await fs19.writeFile(fullPath, content);
|
|
5305
5483
|
count++;
|
|
5306
5484
|
}
|
|
5307
5485
|
await setConfigFlag13(projectRoot, ctx);
|
|
@@ -5321,12 +5499,12 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5321
5499
|
}
|
|
5322
5500
|
}
|
|
5323
5501
|
async function setConfigFlag13(projectRoot, ctx) {
|
|
5324
|
-
const configPath =
|
|
5325
|
-
if (!await
|
|
5502
|
+
const configPath = path20.join(projectRoot, "src", "config", "app.config.ts");
|
|
5503
|
+
if (!await fs19.pathExists(configPath)) return;
|
|
5326
5504
|
await ctx.trackModifiedFile(configPath);
|
|
5327
|
-
const content = await
|
|
5505
|
+
const content = await fs19.readFile(configPath, "utf-8");
|
|
5328
5506
|
const updated = content.replace(/featureFlags:\s*false/, "featureFlags: true");
|
|
5329
|
-
await
|
|
5507
|
+
await fs19.writeFile(configPath, updated);
|
|
5330
5508
|
}
|
|
5331
5509
|
function types12() {
|
|
5332
5510
|
return `${STAMP13}
|
|
@@ -5503,7 +5681,7 @@ function componentsIndex3() {
|
|
|
5503
5681
|
export { FeatureGate } from './FeatureGate';
|
|
5504
5682
|
`;
|
|
5505
5683
|
}
|
|
5506
|
-
function
|
|
5684
|
+
function featureIndex7() {
|
|
5507
5685
|
return `${STAMP13}
|
|
5508
5686
|
export { getFlag } from './server';
|
|
5509
5687
|
export { useFeatureFlag } from './hooks/use-feature-flag';
|
|
@@ -5564,27 +5742,43 @@ var init_feature_flags = __esm({
|
|
|
5564
5742
|
// src/index.ts
|
|
5565
5743
|
import { Command } from "commander";
|
|
5566
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
|
+
|
|
5567
5761
|
// src/commands/create.ts
|
|
5568
5762
|
init_logger();
|
|
5569
|
-
import
|
|
5570
|
-
import
|
|
5763
|
+
import fs21 from "fs-extra";
|
|
5764
|
+
import path22 from "path";
|
|
5571
5765
|
import os3 from "os";
|
|
5572
5766
|
import ora from "ora";
|
|
5573
5767
|
import pc2 from "picocolors";
|
|
5574
5768
|
import prompts5 from "prompts";
|
|
5575
5769
|
|
|
5576
5770
|
// src/utils/template.ts
|
|
5577
|
-
import
|
|
5578
|
-
import { fileURLToPath } from "url";
|
|
5771
|
+
import path2 from "path";
|
|
5772
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5579
5773
|
import fs from "fs-extra";
|
|
5580
|
-
var
|
|
5774
|
+
var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
5581
5775
|
function getTemplatePath() {
|
|
5582
5776
|
const candidates = [
|
|
5583
5777
|
// Built bundle: __dirname = packages/cli/dist/
|
|
5584
|
-
|
|
5585
|
-
|
|
5778
|
+
path2.resolve(__dirname2, "..", "template"),
|
|
5779
|
+
path2.resolve(__dirname2, "..", "..", "..", "template"),
|
|
5586
5780
|
// Source (vitest): __dirname = packages/cli/src/utils/
|
|
5587
|
-
|
|
5781
|
+
path2.resolve(__dirname2, "..", "..", "..", "..", "template")
|
|
5588
5782
|
];
|
|
5589
5783
|
for (const candidate of candidates) {
|
|
5590
5784
|
if (fs.existsSync(candidate)) {
|
|
@@ -5596,7 +5790,7 @@ function getTemplatePath() {
|
|
|
5596
5790
|
);
|
|
5597
5791
|
}
|
|
5598
5792
|
function resolveProjectPath(projectName) {
|
|
5599
|
-
return
|
|
5793
|
+
return path2.resolve(process.cwd(), projectName);
|
|
5600
5794
|
}
|
|
5601
5795
|
|
|
5602
5796
|
// src/prompts/project-info.ts
|
|
@@ -5946,7 +6140,6 @@ var DESIGN_DIRECTIONS = [
|
|
|
5946
6140
|
function getDefaultTheme() {
|
|
5947
6141
|
return {
|
|
5948
6142
|
primaryColor: COLOR_PRESETS[0].value,
|
|
5949
|
-
secondaryColor: "amber-400",
|
|
5950
6143
|
font: FONT_CHOICES[0].value,
|
|
5951
6144
|
designDirection: DESIGN_DIRECTIONS[0].value
|
|
5952
6145
|
};
|
|
@@ -5987,7 +6180,6 @@ async function promptTheme() {
|
|
|
5987
6180
|
if (response.primaryColor === void 0) return null;
|
|
5988
6181
|
return {
|
|
5989
6182
|
primaryColor: response.primaryColor,
|
|
5990
|
-
secondaryColor: "amber-400",
|
|
5991
6183
|
font: response.font,
|
|
5992
6184
|
designDirection: response.designDirection
|
|
5993
6185
|
};
|
|
@@ -5996,7 +6188,7 @@ async function promptTheme() {
|
|
|
5996
6188
|
// src/generators/scaffold.ts
|
|
5997
6189
|
init_logger();
|
|
5998
6190
|
import fs2 from "fs-extra";
|
|
5999
|
-
import
|
|
6191
|
+
import path3 from "path";
|
|
6000
6192
|
import { generateBrandCss } from "@mars-stack/ui/utils";
|
|
6001
6193
|
|
|
6002
6194
|
// src/generators/app-config.ts
|
|
@@ -6039,15 +6231,7 @@ function generateAppConfig(config) {
|
|
|
6039
6231
|
address: '',
|
|
6040
6232
|
},
|
|
6041
6233
|
theme: {
|
|
6042
|
-
primaryColor: '${config.theme.primaryColor}' as string,
|
|
6043
|
-
secondaryColor: '${config.theme.secondaryColor}' as string,
|
|
6044
6234
|
font: '${config.theme.font}' as string,
|
|
6045
|
-
designDirection: '${config.theme.designDirection}' as
|
|
6046
|
-
| 'modern-saas'
|
|
6047
|
-
| 'minimal'
|
|
6048
|
-
| 'enterprise'
|
|
6049
|
-
| 'creative'
|
|
6050
|
-
| 'dashboard',
|
|
6051
6235
|
},
|
|
6052
6236
|
features: {
|
|
6053
6237
|
${featureEntries}
|
|
@@ -6190,8 +6374,8 @@ async function copyTemplateFiles(templateDir, targetDir) {
|
|
|
6190
6374
|
async function walkAndCopy(src, dest) {
|
|
6191
6375
|
const entries = await fs2.readdir(src, { withFileTypes: true });
|
|
6192
6376
|
for (const entry of entries) {
|
|
6193
|
-
const srcPath =
|
|
6194
|
-
const destPath =
|
|
6377
|
+
const srcPath = path3.join(src, entry.name);
|
|
6378
|
+
const destPath = path3.join(dest, entry.name);
|
|
6195
6379
|
if (entry.isDirectory()) {
|
|
6196
6380
|
if (IGNORED_DIRS.includes(entry.name)) continue;
|
|
6197
6381
|
await fs2.ensureDir(destPath);
|
|
@@ -6209,8 +6393,8 @@ async function copyTemplateFiles(templateDir, targetDir) {
|
|
|
6209
6393
|
function resolveMarsPackageRange(packageName, templateDir) {
|
|
6210
6394
|
const shortName = packageName.replace("@mars-stack/", "");
|
|
6211
6395
|
const candidates = [
|
|
6212
|
-
|
|
6213
|
-
|
|
6396
|
+
path3.resolve(templateDir, "..", "packages", shortName, "package.json"),
|
|
6397
|
+
path3.resolve(templateDir, "..", "..", "packages", shortName, "package.json")
|
|
6214
6398
|
];
|
|
6215
6399
|
for (const candidate of candidates) {
|
|
6216
6400
|
if (fs2.existsSync(candidate)) {
|
|
@@ -6221,7 +6405,7 @@ function resolveMarsPackageRange(packageName, templateDir) {
|
|
|
6221
6405
|
}
|
|
6222
6406
|
}
|
|
6223
6407
|
}
|
|
6224
|
-
const templatePkg = fs2.readJsonSync(
|
|
6408
|
+
const templatePkg = fs2.readJsonSync(path3.join(templateDir, "package.json"));
|
|
6225
6409
|
const existing = templatePkg.dependencies?.[packageName];
|
|
6226
6410
|
if (existing && existing !== "*") {
|
|
6227
6411
|
if (/^[\^~>=]/.test(existing)) return existing;
|
|
@@ -6232,7 +6416,7 @@ function resolveMarsPackageRange(packageName, templateDir) {
|
|
|
6232
6416
|
}
|
|
6233
6417
|
function generatePackageJson(config) {
|
|
6234
6418
|
const templateDir = getTemplatePath();
|
|
6235
|
-
const templatePkg =
|
|
6419
|
+
const templatePkg = path3.join(templateDir, "package.json");
|
|
6236
6420
|
const pkg = fs2.readJsonSync(templatePkg);
|
|
6237
6421
|
pkg.name = config.name;
|
|
6238
6422
|
pkg.version = "0.1.0";
|
|
@@ -6432,7 +6616,7 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6432
6616
|
const flagValue = features[feature];
|
|
6433
6617
|
if (flagValue !== false) continue;
|
|
6434
6618
|
for (const relativePath of paths) {
|
|
6435
|
-
const fullPath =
|
|
6619
|
+
const fullPath = path3.join(targetDir, relativePath);
|
|
6436
6620
|
if (await fs2.pathExists(fullPath)) {
|
|
6437
6621
|
await fs2.remove(fullPath);
|
|
6438
6622
|
log.step(`Pruned disabled feature path: ${relativePath}`);
|
|
@@ -6443,7 +6627,7 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6443
6627
|
}
|
|
6444
6628
|
}
|
|
6445
6629
|
if (relationsToRemove.length > 0) {
|
|
6446
|
-
const authPath =
|
|
6630
|
+
const authPath = path3.join(targetDir, "prisma", "schema", "auth.prisma");
|
|
6447
6631
|
if (await fs2.pathExists(authPath)) {
|
|
6448
6632
|
let content = await fs2.readFile(authPath, "utf-8");
|
|
6449
6633
|
for (const field of relationsToRemove) {
|
|
@@ -6461,22 +6645,22 @@ async function scaffoldProject(targetDir, config) {
|
|
|
6461
6645
|
await fs2.ensureDir(targetDir);
|
|
6462
6646
|
const fileCount = await copyTemplateFiles(templateDir, targetDir);
|
|
6463
6647
|
await pruneDisabledFeatures(config.features, targetDir);
|
|
6464
|
-
await fs2.writeFile(
|
|
6648
|
+
await fs2.writeFile(path3.join(targetDir, "package.json"), generatePackageJson(config));
|
|
6465
6649
|
await fs2.writeFile(
|
|
6466
|
-
|
|
6650
|
+
path3.join(targetDir, "src", "config", "app.config.ts"),
|
|
6467
6651
|
generateAppConfig(config)
|
|
6468
6652
|
);
|
|
6469
|
-
await fs2.writeFile(
|
|
6470
|
-
await fs2.writeFile(
|
|
6471
|
-
await fs2.writeFile(
|
|
6472
|
-
await fs2.writeFile(
|
|
6473
|
-
await fs2.writeFile(
|
|
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());
|
|
6474
6658
|
await patchGlobalsCssForScaffoldedProject(targetDir, config);
|
|
6475
6659
|
await generateBrandCssForProject(targetDir, config);
|
|
6476
6660
|
return { fileCount };
|
|
6477
6661
|
}
|
|
6478
6662
|
async function generateBrandCssForProject(targetDir, config) {
|
|
6479
|
-
const brandPath =
|
|
6663
|
+
const brandPath = path3.join(targetDir, "src", "styles", "brand.css");
|
|
6480
6664
|
if (!await fs2.pathExists(brandPath)) return;
|
|
6481
6665
|
const css = generateBrandCss(config.theme.primaryColor);
|
|
6482
6666
|
await fs2.writeFile(brandPath, css);
|
|
@@ -6489,7 +6673,7 @@ var VALID_DESIGN_DIRECTIONS = [
|
|
|
6489
6673
|
"dashboard"
|
|
6490
6674
|
];
|
|
6491
6675
|
async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
6492
|
-
const globalsPath =
|
|
6676
|
+
const globalsPath = path3.join(targetDir, "src", "styles", "globals.css");
|
|
6493
6677
|
if (!await fs2.pathExists(globalsPath)) return;
|
|
6494
6678
|
let content = await fs2.readFile(globalsPath, "utf-8");
|
|
6495
6679
|
content = content.replace(
|
|
@@ -6626,13 +6810,13 @@ async function generateSelectedFeatures(projectRoot, features) {
|
|
|
6626
6810
|
}
|
|
6627
6811
|
|
|
6628
6812
|
// src/utils/telemetry.ts
|
|
6629
|
-
import
|
|
6630
|
-
import
|
|
6813
|
+
import fs20 from "fs-extra";
|
|
6814
|
+
import path21 from "path";
|
|
6631
6815
|
import os2 from "os";
|
|
6632
|
-
var RC_PATH =
|
|
6816
|
+
var RC_PATH = path21.join(os2.homedir(), ".marsrc");
|
|
6633
6817
|
function isTelemetryEnabled() {
|
|
6634
6818
|
try {
|
|
6635
|
-
const config =
|
|
6819
|
+
const config = fs20.readJsonSync(RC_PATH);
|
|
6636
6820
|
return config.enabled === true;
|
|
6637
6821
|
} catch {
|
|
6638
6822
|
return false;
|
|
@@ -6641,16 +6825,16 @@ function isTelemetryEnabled() {
|
|
|
6641
6825
|
function enableTelemetry() {
|
|
6642
6826
|
const config = loadOrCreateConfig();
|
|
6643
6827
|
config.enabled = true;
|
|
6644
|
-
|
|
6828
|
+
fs20.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
6645
6829
|
}
|
|
6646
6830
|
function disableTelemetry() {
|
|
6647
6831
|
const config = loadOrCreateConfig();
|
|
6648
6832
|
config.enabled = false;
|
|
6649
|
-
|
|
6833
|
+
fs20.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
6650
6834
|
}
|
|
6651
6835
|
function loadOrCreateConfig() {
|
|
6652
6836
|
try {
|
|
6653
|
-
return
|
|
6837
|
+
return fs20.readJsonSync(RC_PATH);
|
|
6654
6838
|
} catch {
|
|
6655
6839
|
return { enabled: false, anonymousId: crypto.randomUUID() };
|
|
6656
6840
|
}
|
|
@@ -6670,13 +6854,13 @@ function trackEvent(event, properties) {
|
|
|
6670
6854
|
},
|
|
6671
6855
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6672
6856
|
};
|
|
6673
|
-
const logPath =
|
|
6674
|
-
|
|
6857
|
+
const logPath = path21.join(os2.homedir(), ".mars-telemetry.log");
|
|
6858
|
+
fs20.appendFile(logPath, JSON.stringify(payload) + "\n").catch(() => {
|
|
6675
6859
|
});
|
|
6676
6860
|
}
|
|
6677
6861
|
|
|
6678
6862
|
// src/commands/create.ts
|
|
6679
|
-
var RC_PATH2 =
|
|
6863
|
+
var RC_PATH2 = path22.join(os3.homedir(), ".marsrc");
|
|
6680
6864
|
var PROJECT_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
6681
6865
|
var MAX_PROJECT_NAME_LENGTH = 214;
|
|
6682
6866
|
async function createCommand(projectName, options) {
|
|
@@ -6703,8 +6887,8 @@ async function createCommand(projectName, options) {
|
|
|
6703
6887
|
const projectInfo = useDefaults ? getDefaultProjectInfo(projectName) : await promptProjectInfo(projectName);
|
|
6704
6888
|
if (!projectInfo) return;
|
|
6705
6889
|
const targetDir = resolveProjectPath(projectInfo.name);
|
|
6706
|
-
if (await
|
|
6707
|
-
const entries = await
|
|
6890
|
+
if (await fs21.pathExists(targetDir)) {
|
|
6891
|
+
const entries = await fs21.readdir(targetDir);
|
|
6708
6892
|
if (entries.length > 0) {
|
|
6709
6893
|
log.error(`Directory "${projectInfo.name}" already exists and is not empty.`);
|
|
6710
6894
|
return;
|
|
@@ -6745,7 +6929,7 @@ async function createCommand(projectName, options) {
|
|
|
6745
6929
|
log.info("Scaffolding cancelled.");
|
|
6746
6930
|
process.exit(0);
|
|
6747
6931
|
}
|
|
6748
|
-
if (!
|
|
6932
|
+
if (!fs21.pathExistsSync(RC_PATH2)) {
|
|
6749
6933
|
const { telemetryOptIn } = await prompts5(
|
|
6750
6934
|
{
|
|
6751
6935
|
type: "confirm",
|
|
@@ -6784,10 +6968,10 @@ async function createCommand(projectName, options) {
|
|
|
6784
6968
|
} catch (err) {
|
|
6785
6969
|
spinner.fail("Failed to scaffold project");
|
|
6786
6970
|
log.error(err instanceof Error ? err.message : String(err));
|
|
6787
|
-
if (await
|
|
6971
|
+
if (await fs21.pathExists(targetDir)) {
|
|
6788
6972
|
const cleanupSpinner = ora("Cleaning up...").start();
|
|
6789
6973
|
try {
|
|
6790
|
-
await
|
|
6974
|
+
await fs21.remove(targetDir);
|
|
6791
6975
|
cleanupSpinner.succeed("Cleaned up partial scaffold");
|
|
6792
6976
|
} catch {
|
|
6793
6977
|
cleanupSpinner.warn(`Could not clean up ${targetDir}. You may need to remove it manually.`);
|
|
@@ -6802,8 +6986,8 @@ function countEnabled(flags) {
|
|
|
6802
6986
|
|
|
6803
6987
|
// src/commands/doctor.ts
|
|
6804
6988
|
init_logger();
|
|
6805
|
-
import
|
|
6806
|
-
import
|
|
6989
|
+
import fs22 from "fs-extra";
|
|
6990
|
+
import path23 from "path";
|
|
6807
6991
|
import { execSync } from "child_process";
|
|
6808
6992
|
function commandExists(cmd) {
|
|
6809
6993
|
try {
|
|
@@ -6823,12 +7007,12 @@ function getVersion(cmd) {
|
|
|
6823
7007
|
async function checkForUpgrades() {
|
|
6824
7008
|
log.blank();
|
|
6825
7009
|
log.title("Upgrade Check");
|
|
6826
|
-
const packageJsonPath =
|
|
6827
|
-
if (!
|
|
7010
|
+
const packageJsonPath = path23.join(process.cwd(), "package.json");
|
|
7011
|
+
if (!fs22.pathExistsSync(packageJsonPath)) {
|
|
6828
7012
|
log.warn("No package.json found \u2014 skipping upgrade check.");
|
|
6829
7013
|
return;
|
|
6830
7014
|
}
|
|
6831
|
-
const packageJson =
|
|
7015
|
+
const packageJson = fs22.readJsonSync(packageJsonPath);
|
|
6832
7016
|
const deps = packageJson.dependencies ?? {};
|
|
6833
7017
|
const devDeps = packageJson.devDependencies ?? {};
|
|
6834
7018
|
const currentRaw = deps["@mars-stack/core"] ?? devDeps["@mars-stack/core"];
|
|
@@ -6890,25 +7074,25 @@ async function doctorCommand(options) {
|
|
|
6890
7074
|
},
|
|
6891
7075
|
{
|
|
6892
7076
|
name: "package.json exists",
|
|
6893
|
-
check: () =>
|
|
7077
|
+
check: () => fs22.pathExistsSync(path23.join(process.cwd(), "package.json"))
|
|
6894
7078
|
},
|
|
6895
7079
|
{
|
|
6896
7080
|
name: ".env file exists",
|
|
6897
|
-
check: () =>
|
|
7081
|
+
check: () => fs22.pathExistsSync(path23.join(process.cwd(), ".env"))
|
|
6898
7082
|
},
|
|
6899
7083
|
{
|
|
6900
7084
|
name: "Prisma schema exists",
|
|
6901
|
-
check: () =>
|
|
7085
|
+
check: () => fs22.pathExistsSync(path23.join(process.cwd(), "prisma", "schema")) || fs22.pathExistsSync(path23.join(process.cwd(), "prisma", "schema.prisma"))
|
|
6902
7086
|
},
|
|
6903
7087
|
{
|
|
6904
7088
|
name: "node_modules installed",
|
|
6905
|
-
check: () =>
|
|
7089
|
+
check: () => fs22.pathExistsSync(path23.join(process.cwd(), "node_modules"))
|
|
6906
7090
|
},
|
|
6907
7091
|
{
|
|
6908
7092
|
name: "DATABASE_URL set",
|
|
6909
7093
|
check: () => {
|
|
6910
7094
|
try {
|
|
6911
|
-
const envContent =
|
|
7095
|
+
const envContent = fs22.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
|
|
6912
7096
|
const match = envContent.match(/^DATABASE_URL=(.+)$/m);
|
|
6913
7097
|
return match ? match[1] !== "" : false;
|
|
6914
7098
|
} catch {
|
|
@@ -6920,7 +7104,7 @@ async function doctorCommand(options) {
|
|
|
6920
7104
|
name: "JWT_SECRET set",
|
|
6921
7105
|
check: () => {
|
|
6922
7106
|
try {
|
|
6923
|
-
const envContent =
|
|
7107
|
+
const envContent = fs22.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
|
|
6924
7108
|
const match = envContent.match(/^JWT_SECRET=(.+)$/m);
|
|
6925
7109
|
if (!match || !match[1]) return false;
|
|
6926
7110
|
return match[1].length >= 32 ? true : "set but too short (need >=32 chars)";
|
|
@@ -6965,16 +7149,16 @@ async function doctorCommand(options) {
|
|
|
6965
7149
|
|
|
6966
7150
|
// src/commands/add.ts
|
|
6967
7151
|
init_logger();
|
|
6968
|
-
import
|
|
6969
|
-
import
|
|
7152
|
+
import fs24 from "fs-extra";
|
|
7153
|
+
import path25 from "path";
|
|
6970
7154
|
import pc3 from "picocolors";
|
|
6971
7155
|
|
|
6972
7156
|
// src/utils/doc-updater.ts
|
|
6973
|
-
import
|
|
6974
|
-
import
|
|
7157
|
+
import fs23 from "fs-extra";
|
|
7158
|
+
import path24 from "path";
|
|
6975
7159
|
async function appendToDbSchema(projectDir, modelName, fields) {
|
|
6976
|
-
const filePath =
|
|
6977
|
-
if (!await
|
|
7160
|
+
const filePath = path24.join(projectDir, "docs", "generated", "db-schema.md");
|
|
7161
|
+
if (!await fs23.pathExists(filePath)) return;
|
|
6978
7162
|
const entry = [
|
|
6979
7163
|
"",
|
|
6980
7164
|
`### ${modelName}`,
|
|
@@ -6984,12 +7168,12 @@ async function appendToDbSchema(projectDir, modelName, fields) {
|
|
|
6984
7168
|
...fields.map((f) => `| ${f} | |`),
|
|
6985
7169
|
""
|
|
6986
7170
|
].join("\n");
|
|
6987
|
-
await
|
|
7171
|
+
await fs23.appendFile(filePath, entry);
|
|
6988
7172
|
}
|
|
6989
7173
|
async function updateQualityScore(projectDir, domain, grade) {
|
|
6990
|
-
const filePath =
|
|
6991
|
-
if (!await
|
|
6992
|
-
const content = await
|
|
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");
|
|
6993
7177
|
const pattern = new RegExp(`^\\|\\s*${escapeRegex(domain)}\\s*\\|`, "m");
|
|
6994
7178
|
if (pattern.test(content)) {
|
|
6995
7179
|
const updated = content.replace(pattern, (match) => {
|
|
@@ -6997,11 +7181,11 @@ async function updateQualityScore(projectDir, domain, grade) {
|
|
|
6997
7181
|
parts[2] = ` ${grade} `;
|
|
6998
7182
|
return parts.join("|");
|
|
6999
7183
|
});
|
|
7000
|
-
await
|
|
7184
|
+
await fs23.writeFile(filePath, updated);
|
|
7001
7185
|
} else {
|
|
7002
7186
|
const row = `| ${domain} | ${grade} | |
|
|
7003
7187
|
`;
|
|
7004
|
-
await
|
|
7188
|
+
await fs23.appendFile(filePath, row);
|
|
7005
7189
|
}
|
|
7006
7190
|
}
|
|
7007
7191
|
function escapeRegex(str) {
|
|
@@ -7012,8 +7196,8 @@ function escapeRegex(str) {
|
|
|
7012
7196
|
init_rollback();
|
|
7013
7197
|
function ensureInProject() {
|
|
7014
7198
|
const cwd = process.cwd();
|
|
7015
|
-
const configPath =
|
|
7016
|
-
if (!
|
|
7199
|
+
const configPath = path25.join(cwd, "src", "config", "app.config.ts");
|
|
7200
|
+
if (!fs24.pathExistsSync(configPath)) {
|
|
7017
7201
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7018
7202
|
process.exit(1);
|
|
7019
7203
|
}
|
|
@@ -7046,8 +7230,8 @@ async function addFeatureCommand(name) {
|
|
|
7046
7230
|
const kebab = toKebab(name);
|
|
7047
7231
|
const pascal = toPascal(name);
|
|
7048
7232
|
const camel = toCamel(name);
|
|
7049
|
-
const featureDir =
|
|
7050
|
-
if (await
|
|
7233
|
+
const featureDir = path25.join(root, "src", "features", kebab);
|
|
7234
|
+
if (await fs24.pathExists(featureDir)) {
|
|
7051
7235
|
log.error(`Feature "${kebab}" already exists at src/features/${kebab}/`);
|
|
7052
7236
|
return;
|
|
7053
7237
|
}
|
|
@@ -7112,9 +7296,9 @@ export type Update${pascal}Input = z.infer<typeof ${camel}Schemas.update>;
|
|
|
7112
7296
|
ctx.trackCreatedFile(featureDir);
|
|
7113
7297
|
let count = 0;
|
|
7114
7298
|
for (const [filePath, content] of Object.entries(files)) {
|
|
7115
|
-
const fullPath =
|
|
7116
|
-
await
|
|
7117
|
-
await
|
|
7299
|
+
const fullPath = path25.join(featureDir, filePath);
|
|
7300
|
+
await fs24.ensureDir(path25.dirname(fullPath));
|
|
7301
|
+
await fs24.writeFile(fullPath, content);
|
|
7118
7302
|
count++;
|
|
7119
7303
|
}
|
|
7120
7304
|
await ctx.commit();
|
|
@@ -7135,8 +7319,8 @@ async function addPageCommand(routePath, options) {
|
|
|
7135
7319
|
const root = ensureInProject();
|
|
7136
7320
|
const cleanPath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
|
|
7137
7321
|
const group = options.protected ? "(protected)" : "(public)";
|
|
7138
|
-
const pageDir =
|
|
7139
|
-
if (await
|
|
7322
|
+
const pageDir = path25.join(root, "src", "app", group, cleanPath);
|
|
7323
|
+
if (await fs24.pathExists(path25.join(pageDir, "page.tsx"))) {
|
|
7140
7324
|
log.error(`Page already exists at src/app/${group}/${cleanPath}/page.tsx`);
|
|
7141
7325
|
return;
|
|
7142
7326
|
}
|
|
@@ -7184,9 +7368,9 @@ export default function Loading() {
|
|
|
7184
7368
|
const ctx = createRollbackContext();
|
|
7185
7369
|
try {
|
|
7186
7370
|
ctx.trackCreatedFile(pageDir);
|
|
7187
|
-
await
|
|
7188
|
-
await
|
|
7189
|
-
await
|
|
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);
|
|
7190
7374
|
await ctx.commit();
|
|
7191
7375
|
log.success(`Created page at ${pc3.bold(`src/app/${group}/${cleanPath}/`)}`);
|
|
7192
7376
|
trackEvent("add", { type: "page" });
|
|
@@ -7203,9 +7387,9 @@ async function addModelCommand(name) {
|
|
|
7203
7387
|
const pascal = toPascal(name);
|
|
7204
7388
|
const camel = toCamel(name);
|
|
7205
7389
|
const kebab = toKebab(name);
|
|
7206
|
-
const schemaDir =
|
|
7207
|
-
const schemaFile =
|
|
7208
|
-
if (await
|
|
7390
|
+
const schemaDir = path25.join(root, "prisma", "schema");
|
|
7391
|
+
const schemaFile = path25.join(schemaDir, `${kebab}.prisma`);
|
|
7392
|
+
if (await fs24.pathExists(schemaFile)) {
|
|
7209
7393
|
log.error(`Schema file already exists: prisma/schema/${kebab}.prisma`);
|
|
7210
7394
|
return;
|
|
7211
7395
|
}
|
|
@@ -7223,8 +7407,8 @@ async function addModelCommand(name) {
|
|
|
7223
7407
|
const ctx = createRollbackContext();
|
|
7224
7408
|
try {
|
|
7225
7409
|
ctx.trackCreatedFile(schemaFile);
|
|
7226
|
-
await
|
|
7227
|
-
await
|
|
7410
|
+
await fs24.ensureDir(schemaDir);
|
|
7411
|
+
await fs24.writeFile(schemaFile, content);
|
|
7228
7412
|
await ctx.commit();
|
|
7229
7413
|
log.success(`Created model ${pc3.bold(pascal)} at prisma/schema/${kebab}.prisma`);
|
|
7230
7414
|
trackEvent("add", { type: "model" });
|
|
@@ -7246,9 +7430,9 @@ async function addEmailCommand(name) {
|
|
|
7246
7430
|
const kebab = toKebab(name);
|
|
7247
7431
|
const pascal = toPascal(name);
|
|
7248
7432
|
const camel = toCamel(name);
|
|
7249
|
-
const templatesDir =
|
|
7250
|
-
const filePath =
|
|
7251
|
-
if (await
|
|
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)) {
|
|
7252
7436
|
log.error(`Email template already exists: src/lib/core/email/templates/${kebab}-email.ts`);
|
|
7253
7437
|
return;
|
|
7254
7438
|
}
|
|
@@ -7292,25 +7476,25 @@ export function ${functionName}({ appName, actionUrl, userName }: ${pascal}Email
|
|
|
7292
7476
|
}
|
|
7293
7477
|
`;
|
|
7294
7478
|
const ctx = createRollbackContext();
|
|
7295
|
-
const indexPath =
|
|
7479
|
+
const indexPath = path25.join(templatesDir, "index.ts");
|
|
7296
7480
|
try {
|
|
7297
7481
|
ctx.trackCreatedFile(filePath);
|
|
7298
|
-
if (await
|
|
7482
|
+
if (await fs24.pathExists(indexPath)) {
|
|
7299
7483
|
await ctx.trackModifiedFile(indexPath);
|
|
7300
7484
|
} else {
|
|
7301
7485
|
ctx.trackCreatedFile(indexPath);
|
|
7302
7486
|
}
|
|
7303
|
-
await
|
|
7304
|
-
await
|
|
7487
|
+
await fs24.ensureDir(templatesDir);
|
|
7488
|
+
await fs24.writeFile(filePath, content);
|
|
7305
7489
|
const exportLine = `export { ${functionName} } from './${kebab}-email';
|
|
7306
7490
|
`;
|
|
7307
|
-
if (await
|
|
7308
|
-
const existing = await
|
|
7491
|
+
if (await fs24.pathExists(indexPath)) {
|
|
7492
|
+
const existing = await fs24.readFile(indexPath, "utf-8");
|
|
7309
7493
|
if (!existing.includes(functionName)) {
|
|
7310
|
-
await
|
|
7494
|
+
await fs24.appendFile(indexPath, exportLine);
|
|
7311
7495
|
}
|
|
7312
7496
|
} else {
|
|
7313
|
-
await
|
|
7497
|
+
await fs24.writeFile(indexPath, exportLine);
|
|
7314
7498
|
}
|
|
7315
7499
|
await ctx.commit();
|
|
7316
7500
|
log.success(`Created email template ${pc3.bold(kebab)} at src/lib/core/email/templates/${kebab}-email.ts`);
|
|
@@ -7333,10 +7517,10 @@ async function addComponentCommand(name, options) {
|
|
|
7333
7517
|
log.error(`Invalid type "${type}". Use: ${validTypes.join(", ")}`);
|
|
7334
7518
|
return;
|
|
7335
7519
|
}
|
|
7336
|
-
const dir = type === "primitive" ?
|
|
7337
|
-
await
|
|
7338
|
-
const filePath =
|
|
7339
|
-
if (await
|
|
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)) {
|
|
7340
7524
|
log.error(`Component already exists: ${pascal}.tsx`);
|
|
7341
7525
|
return;
|
|
7342
7526
|
}
|
|
@@ -7385,9 +7569,9 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7385
7569
|
const ctx = createRollbackContext();
|
|
7386
7570
|
try {
|
|
7387
7571
|
ctx.trackCreatedFile(filePath);
|
|
7388
|
-
await
|
|
7572
|
+
await fs24.writeFile(filePath, content);
|
|
7389
7573
|
await ctx.commit();
|
|
7390
|
-
log.success(`Created ${type} component ${pc3.bold(pascal)} at ${
|
|
7574
|
+
log.success(`Created ${type} component ${pc3.bold(pascal)} at ${path25.relative(root, filePath)}`);
|
|
7391
7575
|
trackEvent("add", { type: "component", componentType: type });
|
|
7392
7576
|
log.blank();
|
|
7393
7577
|
log.warn(`Remember to add the export to the barrel file:`);
|
|
@@ -7402,14 +7586,14 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7402
7586
|
// src/commands/configure.ts
|
|
7403
7587
|
init_logger();
|
|
7404
7588
|
import { execSync as execSync2 } from "child_process";
|
|
7405
|
-
import
|
|
7406
|
-
import
|
|
7589
|
+
import fs25 from "fs-extra";
|
|
7590
|
+
import path26 from "path";
|
|
7407
7591
|
import pc4 from "picocolors";
|
|
7408
7592
|
import prompts6 from "prompts";
|
|
7409
7593
|
function ensureInProject2() {
|
|
7410
7594
|
const cwd = process.cwd();
|
|
7411
|
-
const configPath =
|
|
7412
|
-
if (!
|
|
7595
|
+
const configPath = path26.join(cwd, "src", "config", "app.config.ts");
|
|
7596
|
+
if (!fs25.pathExistsSync(configPath)) {
|
|
7413
7597
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7414
7598
|
process.exit(1);
|
|
7415
7599
|
}
|
|
@@ -7423,8 +7607,8 @@ var PROVIDER_DEPENDENCIES = {
|
|
|
7423
7607
|
"storage:s3": ["@aws-sdk/client-s3", "@aws-sdk/s3-request-presigner"]
|
|
7424
7608
|
};
|
|
7425
7609
|
function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
7426
|
-
const configPath =
|
|
7427
|
-
let content =
|
|
7610
|
+
const configPath = path26.join(projectDir, "src", "config", "app.config.ts");
|
|
7611
|
+
let content = fs25.readFileSync(configPath, "utf-8");
|
|
7428
7612
|
if (serviceKey === "auth") {
|
|
7429
7613
|
const boolValue = provider === "google" ? "true" : "false";
|
|
7430
7614
|
const featureRegex = new RegExp(`(googleOAuth\\s*:\\s*)(?:true|false)`);
|
|
@@ -7439,10 +7623,10 @@ function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
|
7439
7623
|
content = content.replace(featureRegex, `$1true`);
|
|
7440
7624
|
}
|
|
7441
7625
|
}
|
|
7442
|
-
|
|
7626
|
+
fs25.writeFileSync(configPath, content);
|
|
7443
7627
|
}
|
|
7444
7628
|
function detectPackageManager(projectDir) {
|
|
7445
|
-
if (
|
|
7629
|
+
if (fs25.existsSync(path26.join(projectDir, "yarn.lock"))) return "yarn";
|
|
7446
7630
|
return "npm";
|
|
7447
7631
|
}
|
|
7448
7632
|
function installDependencies(projectDir, deps) {
|
|
@@ -7552,15 +7736,15 @@ async function configureCommand(service) {
|
|
|
7552
7736
|
log.step(`Manually set ${pc4.bold(`services.${serviceConfig.configKey}.provider`)} to ${pc4.bold(`'${provider}'`)} in ${pc4.dim("src/config/app.config.ts")}`);
|
|
7553
7737
|
}
|
|
7554
7738
|
if (selectedProvider.envVars.length > 0) {
|
|
7555
|
-
const envPath =
|
|
7556
|
-
if (await
|
|
7557
|
-
const envContent = await
|
|
7739
|
+
const envPath = path26.join(root, ".env");
|
|
7740
|
+
if (await fs25.pathExists(envPath)) {
|
|
7741
|
+
const envContent = await fs25.readFile(envPath, "utf-8");
|
|
7558
7742
|
const missingVars = selectedProvider.envVars.filter(
|
|
7559
7743
|
(v) => !envContent.includes(v)
|
|
7560
7744
|
);
|
|
7561
7745
|
if (missingVars.length > 0) {
|
|
7562
7746
|
const additions = missingVars.map((v) => `${v}=""`).join("\n");
|
|
7563
|
-
await
|
|
7747
|
+
await fs25.appendFile(envPath, `
|
|
7564
7748
|
# ${selectedService} (${provider})
|
|
7565
7749
|
${additions}
|
|
7566
7750
|
`);
|
|
@@ -7589,15 +7773,15 @@ ${additions}
|
|
|
7589
7773
|
|
|
7590
7774
|
// src/commands/deploy.ts
|
|
7591
7775
|
init_logger();
|
|
7592
|
-
import
|
|
7593
|
-
import
|
|
7776
|
+
import fs26 from "fs-extra";
|
|
7777
|
+
import path27 from "path";
|
|
7594
7778
|
import { execSync as execSync3 } from "child_process";
|
|
7595
7779
|
import pc5 from "picocolors";
|
|
7596
7780
|
import prompts7 from "prompts";
|
|
7597
7781
|
function ensureInProject3() {
|
|
7598
7782
|
const cwd = process.cwd();
|
|
7599
|
-
const configPath =
|
|
7600
|
-
if (!
|
|
7783
|
+
const configPath = path27.join(cwd, "src", "config", "app.config.ts");
|
|
7784
|
+
if (!fs26.pathExistsSync(configPath)) {
|
|
7601
7785
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7602
7786
|
process.exit(1);
|
|
7603
7787
|
}
|
|
@@ -7636,8 +7820,8 @@ async function deployCommand() {
|
|
|
7636
7820
|
return;
|
|
7637
7821
|
}
|
|
7638
7822
|
}
|
|
7639
|
-
const vercelDir =
|
|
7640
|
-
if (!
|
|
7823
|
+
const vercelDir = path27.join(root, ".vercel");
|
|
7824
|
+
if (!fs26.existsSync(vercelDir)) {
|
|
7641
7825
|
log.step("Linking project to Vercel...");
|
|
7642
7826
|
try {
|
|
7643
7827
|
execSync3("vercel link", { cwd: root, stdio: "inherit" });
|
|
@@ -7654,8 +7838,8 @@ async function deployCommand() {
|
|
|
7654
7838
|
log.blank();
|
|
7655
7839
|
const requiredVars = ["JWT_SECRET", "DATABASE_URL"];
|
|
7656
7840
|
log.step(`Core: ${pc5.dim(requiredVars.join(", "))}`);
|
|
7657
|
-
const configPath =
|
|
7658
|
-
const configContent = await
|
|
7841
|
+
const configPath = path27.join(root, "src", "config", "app.config.ts");
|
|
7842
|
+
const configContent = await fs26.readFile(configPath, "utf-8");
|
|
7659
7843
|
if (configContent.includes("email: { provider: 'sendgrid'")) {
|
|
7660
7844
|
log.step(`Email (SendGrid): ${pc5.dim("SENDGRID_API_KEY, SENDGRID_FROM_EMAIL")}`);
|
|
7661
7845
|
} else if (configContent.includes("email: { provider: 'resend'")) {
|
|
@@ -7716,8 +7900,8 @@ async function deployCommand() {
|
|
|
7716
7900
|
|
|
7717
7901
|
// src/commands/upgrade.ts
|
|
7718
7902
|
init_logger();
|
|
7719
|
-
import
|
|
7720
|
-
import
|
|
7903
|
+
import fs27 from "fs-extra";
|
|
7904
|
+
import path28 from "path";
|
|
7721
7905
|
import { execSync as execSync4 } from "child_process";
|
|
7722
7906
|
var MARS_PACKAGES = ["@mars-stack/core", "@mars-stack/ui"];
|
|
7723
7907
|
var CHANGELOG_URL = "https://github.com/greaveselliott/mars/blob/main/CHANGELOG.md";
|
|
@@ -7740,7 +7924,7 @@ function readCurrentVersion(packageJson, packageName) {
|
|
|
7740
7924
|
return version ? version.replace(/^[\^~]/, "") : null;
|
|
7741
7925
|
}
|
|
7742
7926
|
function detectPackageManager2(projectDir) {
|
|
7743
|
-
if (
|
|
7927
|
+
if (fs27.pathExistsSync(path28.join(projectDir, "yarn.lock"))) {
|
|
7744
7928
|
return "yarn";
|
|
7745
7929
|
}
|
|
7746
7930
|
return "npm";
|
|
@@ -7804,7 +7988,7 @@ function updatePackageJsonVersions(packageJsonPath, packageJson, versions) {
|
|
|
7804
7988
|
if (updated.length > 0) {
|
|
7805
7989
|
packageJson.dependencies = deps;
|
|
7806
7990
|
packageJson.devDependencies = devDeps;
|
|
7807
|
-
|
|
7991
|
+
fs27.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
7808
7992
|
}
|
|
7809
7993
|
return updated;
|
|
7810
7994
|
}
|
|
@@ -7814,15 +7998,15 @@ function upgradeCommand(program2) {
|
|
|
7814
7998
|
).option("--dry-run", "Show what would be updated without making changes").action(async (options) => {
|
|
7815
7999
|
log.title("MARS Upgrade");
|
|
7816
8000
|
const projectDir = process.cwd();
|
|
7817
|
-
const packageJsonPath =
|
|
7818
|
-
if (!
|
|
8001
|
+
const packageJsonPath = path28.join(projectDir, "package.json");
|
|
8002
|
+
if (!fs27.pathExistsSync(packageJsonPath)) {
|
|
7819
8003
|
log.error(
|
|
7820
8004
|
"No package.json found. Are you in a Mars project directory?"
|
|
7821
8005
|
);
|
|
7822
8006
|
process.exitCode = 1;
|
|
7823
8007
|
return;
|
|
7824
8008
|
}
|
|
7825
|
-
const packageJson =
|
|
8009
|
+
const packageJson = fs27.readJsonSync(packageJsonPath);
|
|
7826
8010
|
const hasMarsPackage = MARS_PACKAGES.some(
|
|
7827
8011
|
(name) => readCurrentVersion(packageJson, name) !== null
|
|
7828
8012
|
);
|
|
@@ -7918,7 +8102,7 @@ init_sentry();
|
|
|
7918
8102
|
init_feature_flags();
|
|
7919
8103
|
init_logger();
|
|
7920
8104
|
var program = new Command();
|
|
7921
|
-
program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(
|
|
8105
|
+
program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(getCliVersion()).option("-v, --verbose", "Enable verbose output for debugging");
|
|
7922
8106
|
function isVerbose() {
|
|
7923
8107
|
return program.opts().verbose === true;
|
|
7924
8108
|
}
|