@mars-stack/cli 4.0.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +595 -487
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -44,8 +44,8 @@ var init_logger = __esm({
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
// src/utils/rollback.ts
|
|
47
|
-
import
|
|
48
|
-
import
|
|
47
|
+
import fs5 from "fs-extra";
|
|
48
|
+
import path5 from "path";
|
|
49
49
|
import os from "os";
|
|
50
50
|
function createRollbackContext() {
|
|
51
51
|
const manifest = {
|
|
@@ -53,15 +53,15 @@ function createRollbackContext() {
|
|
|
53
53
|
filesModified: [],
|
|
54
54
|
depsInstalled: []
|
|
55
55
|
};
|
|
56
|
-
const backupDir =
|
|
56
|
+
const backupDir = path5.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
|
-
if (!await
|
|
62
|
-
await
|
|
63
|
-
const backupPath =
|
|
64
|
-
await
|
|
61
|
+
if (!await fs5.pathExists(filePath)) return;
|
|
62
|
+
await fs5.ensureDir(backupDir);
|
|
63
|
+
const backupPath = path5.join(backupDir, `${manifest.filesModified.length}-${path5.basename(filePath)}`);
|
|
64
|
+
await fs5.copy(filePath, backupPath);
|
|
65
65
|
manifest.filesModified.push({ path: filePath, backup: backupPath });
|
|
66
66
|
}
|
|
67
67
|
function trackInstalledDep(depName) {
|
|
@@ -70,13 +70,13 @@ function createRollbackContext() {
|
|
|
70
70
|
async function rollback() {
|
|
71
71
|
for (const filePath of manifest.filesCreated.reverse()) {
|
|
72
72
|
try {
|
|
73
|
-
await
|
|
73
|
+
await fs5.remove(filePath);
|
|
74
74
|
} catch {
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
for (const { path: originalPath, backup } of manifest.filesModified) {
|
|
78
78
|
try {
|
|
79
|
-
await
|
|
79
|
+
await fs5.copy(backup, originalPath, { overwrite: true });
|
|
80
80
|
} catch {
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -86,9 +86,9 @@ function createRollbackContext() {
|
|
|
86
86
|
await cleanupBackups();
|
|
87
87
|
}
|
|
88
88
|
async function cleanupBackups() {
|
|
89
|
-
if (await
|
|
89
|
+
if (await fs5.pathExists(backupDir)) {
|
|
90
90
|
try {
|
|
91
|
-
await
|
|
91
|
+
await fs5.remove(backupDir);
|
|
92
92
|
} catch {
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -108,15 +108,15 @@ var init_rollback = __esm({
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
// src/utils/dependencies.ts
|
|
111
|
-
import
|
|
112
|
-
import
|
|
111
|
+
import fs6 from "fs-extra";
|
|
112
|
+
import path6 from "path";
|
|
113
113
|
async function addDependencies(projectRoot, deps, dev = false) {
|
|
114
|
-
const pkgPath =
|
|
115
|
-
if (!await
|
|
116
|
-
const pkg = await
|
|
114
|
+
const pkgPath = path6.join(projectRoot, "package.json");
|
|
115
|
+
if (!await fs6.pathExists(pkgPath)) return;
|
|
116
|
+
const pkg = await fs6.readJson(pkgPath);
|
|
117
117
|
const key = dev ? "devDependencies" : "dependencies";
|
|
118
118
|
pkg[key] = { ...pkg[key], ...deps };
|
|
119
|
-
await
|
|
119
|
+
await fs6.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
120
120
|
}
|
|
121
121
|
var init_dependencies = __esm({
|
|
122
122
|
"src/utils/dependencies.ts"() {
|
|
@@ -125,12 +125,12 @@ var init_dependencies = __esm({
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
// src/utils/routes.ts
|
|
128
|
-
import
|
|
129
|
-
import
|
|
128
|
+
import fs7 from "fs-extra";
|
|
129
|
+
import path7 from "path";
|
|
130
130
|
async function registerRoute(projectRoot, key, routePath, ctx) {
|
|
131
|
-
const routesFile =
|
|
132
|
-
if (!await
|
|
133
|
-
let content = await
|
|
131
|
+
const routesFile = path7.join(projectRoot, "src", "config", "routes.ts");
|
|
132
|
+
if (!await fs7.pathExists(routesFile)) return;
|
|
133
|
+
let content = await fs7.readFile(routesFile, "utf-8");
|
|
134
134
|
if (content.includes(`${key}:`)) return;
|
|
135
135
|
if (ctx) {
|
|
136
136
|
await ctx.trackModifiedFile(routesFile);
|
|
@@ -140,7 +140,7 @@ async function registerRoute(projectRoot, key, routePath, ctx) {
|
|
|
140
140
|
` ${key}: '${routePath}',
|
|
141
141
|
} as const;`
|
|
142
142
|
);
|
|
143
|
-
await
|
|
143
|
+
await fs7.writeFile(routesFile, content);
|
|
144
144
|
}
|
|
145
145
|
var init_routes = __esm({
|
|
146
146
|
"src/utils/routes.ts"() {
|
|
@@ -153,11 +153,11 @@ var blog_exports = {};
|
|
|
153
153
|
__export(blog_exports, {
|
|
154
154
|
generateBlog: () => generateBlog
|
|
155
155
|
});
|
|
156
|
-
import
|
|
157
|
-
import
|
|
156
|
+
import fs8 from "fs-extra";
|
|
157
|
+
import path8 from "path";
|
|
158
158
|
async function generateBlog(projectRoot) {
|
|
159
|
-
const featureDir =
|
|
160
|
-
if (await
|
|
159
|
+
const featureDir = path8.join(projectRoot, "src", "features", "blog");
|
|
160
|
+
if (await fs8.pathExists(featureDir)) {
|
|
161
161
|
log.error("Blog feature already exists at src/features/blog/");
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
@@ -178,10 +178,10 @@ async function generateBlog(projectRoot) {
|
|
|
178
178
|
};
|
|
179
179
|
let count = 0;
|
|
180
180
|
for (const [filePath, content] of Object.entries(files)) {
|
|
181
|
-
const fullPath =
|
|
181
|
+
const fullPath = path8.join(projectRoot, filePath);
|
|
182
182
|
ctx.trackCreatedFile(fullPath);
|
|
183
|
-
await
|
|
184
|
-
await
|
|
183
|
+
await fs8.ensureDir(path8.dirname(fullPath));
|
|
184
|
+
await fs8.writeFile(fullPath, content);
|
|
185
185
|
count++;
|
|
186
186
|
}
|
|
187
187
|
await addDependencies(projectRoot, {
|
|
@@ -208,12 +208,12 @@ async function generateBlog(projectRoot) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
async function setConfigFlag(projectRoot, ctx) {
|
|
211
|
-
const configPath =
|
|
212
|
-
if (!await
|
|
211
|
+
const configPath = path8.join(projectRoot, "src", "config", "app.config.ts");
|
|
212
|
+
if (!await fs8.pathExists(configPath)) return;
|
|
213
213
|
await ctx.trackModifiedFile(configPath);
|
|
214
|
-
const content = await
|
|
214
|
+
const content = await fs8.readFile(configPath, "utf-8");
|
|
215
215
|
const updated = content.replace(/blog:\s*false/, "blog: true");
|
|
216
|
-
await
|
|
216
|
+
await fs8.writeFile(configPath, updated);
|
|
217
217
|
}
|
|
218
218
|
function schemas() {
|
|
219
219
|
return `${STAMP}
|
|
@@ -732,11 +732,11 @@ var dark_mode_exports = {};
|
|
|
732
732
|
__export(dark_mode_exports, {
|
|
733
733
|
generateDarkMode: () => generateDarkMode
|
|
734
734
|
});
|
|
735
|
-
import
|
|
736
|
-
import
|
|
735
|
+
import fs9 from "fs-extra";
|
|
736
|
+
import path9 from "path";
|
|
737
737
|
async function generateDarkMode(projectRoot) {
|
|
738
|
-
const featureDir =
|
|
739
|
-
if (await
|
|
738
|
+
const featureDir = path9.join(projectRoot, "src", "features", "dark-mode");
|
|
739
|
+
if (await fs9.pathExists(featureDir)) {
|
|
740
740
|
log.error("Dark mode feature already exists at src/features/dark-mode/");
|
|
741
741
|
return;
|
|
742
742
|
}
|
|
@@ -751,10 +751,10 @@ async function generateDarkMode(projectRoot) {
|
|
|
751
751
|
};
|
|
752
752
|
let count = 0;
|
|
753
753
|
for (const [filePath, content] of Object.entries(files)) {
|
|
754
|
-
const fullPath =
|
|
754
|
+
const fullPath = path9.join(projectRoot, filePath);
|
|
755
755
|
ctx.trackCreatedFile(fullPath);
|
|
756
|
-
await
|
|
757
|
-
await
|
|
756
|
+
await fs9.ensureDir(path9.dirname(fullPath));
|
|
757
|
+
await fs9.writeFile(fullPath, content);
|
|
758
758
|
count++;
|
|
759
759
|
}
|
|
760
760
|
await wireLayout(projectRoot, ctx);
|
|
@@ -784,18 +784,18 @@ async function generateDarkMode(projectRoot) {
|
|
|
784
784
|
}
|
|
785
785
|
}
|
|
786
786
|
async function setConfigFlag2(projectRoot, ctx) {
|
|
787
|
-
const configPath =
|
|
788
|
-
if (!await
|
|
787
|
+
const configPath = path9.join(projectRoot, "src", "config", "app.config.ts");
|
|
788
|
+
if (!await fs9.pathExists(configPath)) return;
|
|
789
789
|
await ctx.trackModifiedFile(configPath);
|
|
790
|
-
const content = await
|
|
790
|
+
const content = await fs9.readFile(configPath, "utf-8");
|
|
791
791
|
const updated = content.replace(/darkMode:\s*false/, "darkMode: true");
|
|
792
|
-
await
|
|
792
|
+
await fs9.writeFile(configPath, updated);
|
|
793
793
|
}
|
|
794
794
|
async function wireLayout(projectRoot, ctx) {
|
|
795
|
-
const layoutPath =
|
|
796
|
-
if (!await
|
|
795
|
+
const layoutPath = path9.join(projectRoot, "src", "app", "layout.tsx");
|
|
796
|
+
if (!await fs9.pathExists(layoutPath)) return;
|
|
797
797
|
await ctx.trackModifiedFile(layoutPath);
|
|
798
|
-
let content = await
|
|
798
|
+
let content = await fs9.readFile(layoutPath, "utf-8");
|
|
799
799
|
if (!content.includes("getThemeScript")) {
|
|
800
800
|
content = `import { getThemeScript } from '@/features/dark-mode';
|
|
801
801
|
${content}`;
|
|
@@ -809,13 +809,13 @@ ${content}`;
|
|
|
809
809
|
"$1\n <head>\n <script dangerouslySetInnerHTML={{ __html: getThemeScript() }} />\n </head>\n$2"
|
|
810
810
|
);
|
|
811
811
|
}
|
|
812
|
-
await
|
|
812
|
+
await fs9.writeFile(layoutPath, content);
|
|
813
813
|
}
|
|
814
814
|
async function wireProviders(projectRoot, ctx) {
|
|
815
|
-
const providersPath =
|
|
816
|
-
if (!await
|
|
815
|
+
const providersPath = path9.join(projectRoot, "src", "app", "providers.tsx");
|
|
816
|
+
if (!await fs9.pathExists(providersPath)) return;
|
|
817
817
|
await ctx.trackModifiedFile(providersPath);
|
|
818
|
-
let content = await
|
|
818
|
+
let content = await fs9.readFile(providersPath, "utf-8");
|
|
819
819
|
if (content.includes("ThemeProvider")) return;
|
|
820
820
|
content = insertImportAfterDirectives(
|
|
821
821
|
content,
|
|
@@ -834,13 +834,13 @@ async function wireProviders(projectRoot, ctx) {
|
|
|
834
834
|
);`;
|
|
835
835
|
}
|
|
836
836
|
);
|
|
837
|
-
await
|
|
837
|
+
await fs9.writeFile(providersPath, content);
|
|
838
838
|
}
|
|
839
839
|
async function wireProtectedNav(projectRoot, ctx) {
|
|
840
|
-
const navPath =
|
|
841
|
-
if (!await
|
|
840
|
+
const navPath = path9.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
841
|
+
if (!await fs9.pathExists(navPath)) return;
|
|
842
842
|
await ctx.trackModifiedFile(navPath);
|
|
843
|
-
let content = await
|
|
843
|
+
let content = await fs9.readFile(navPath, "utf-8");
|
|
844
844
|
if (content.includes("ThemeToggleSimple")) return;
|
|
845
845
|
content = insertImportAfterDirectives(
|
|
846
846
|
content,
|
|
@@ -855,19 +855,19 @@ async function wireProtectedNav(projectRoot, ctx) {
|
|
|
855
855
|
/(<div className="border-t border-border-default px-4 py-3">\s*\n\s*<UserMenu \/>)/,
|
|
856
856
|
'$1\n <div className="mt-2">\n <ThemeToggleSimple />\n </div>'
|
|
857
857
|
);
|
|
858
|
-
await
|
|
858
|
+
await fs9.writeFile(navPath, content);
|
|
859
859
|
}
|
|
860
860
|
async function wireTailwindDarkMode(projectRoot, ctx) {
|
|
861
|
-
const globalsPath =
|
|
862
|
-
if (!await
|
|
861
|
+
const globalsPath = path9.join(projectRoot, "src", "styles", "globals.css");
|
|
862
|
+
if (!await fs9.pathExists(globalsPath)) return;
|
|
863
863
|
await ctx.trackModifiedFile(globalsPath);
|
|
864
|
-
let content = await
|
|
864
|
+
let content = await fs9.readFile(globalsPath, "utf-8");
|
|
865
865
|
if (content.includes("@custom-variant dark")) return;
|
|
866
866
|
content = content.replace(
|
|
867
867
|
/@import 'tailwindcss';/,
|
|
868
868
|
"@import 'tailwindcss';\n@custom-variant dark (&:where(.dark, .dark *));"
|
|
869
869
|
);
|
|
870
|
-
await
|
|
870
|
+
await fs9.writeFile(globalsPath, content);
|
|
871
871
|
}
|
|
872
872
|
function themeProvider() {
|
|
873
873
|
return `${STAMP2}
|
|
@@ -1147,19 +1147,19 @@ var init_dark_mode = __esm({
|
|
|
1147
1147
|
});
|
|
1148
1148
|
|
|
1149
1149
|
// src/utils/prisma.ts
|
|
1150
|
-
import
|
|
1151
|
-
import
|
|
1150
|
+
import fs10 from "fs-extra";
|
|
1151
|
+
import path10 from "path";
|
|
1152
1152
|
async function addUserRelation(projectRoot, field, ctx) {
|
|
1153
|
-
const authPath =
|
|
1154
|
-
if (!await
|
|
1153
|
+
const authPath = path10.join(projectRoot, "prisma", "schema", "auth.prisma");
|
|
1154
|
+
if (!await fs10.pathExists(authPath)) return;
|
|
1155
1155
|
await ctx.trackModifiedFile(authPath);
|
|
1156
|
-
let content = await
|
|
1156
|
+
let content = await fs10.readFile(authPath, "utf-8");
|
|
1157
1157
|
if (content.includes(field)) return;
|
|
1158
1158
|
const insertBefore = " @@index([email])";
|
|
1159
1159
|
content = content.replace(insertBefore, ` ${field}
|
|
1160
1160
|
|
|
1161
1161
|
${insertBefore}`);
|
|
1162
|
-
await
|
|
1162
|
+
await fs10.writeFile(authPath, content);
|
|
1163
1163
|
}
|
|
1164
1164
|
var init_prisma = __esm({
|
|
1165
1165
|
"src/utils/prisma.ts"() {
|
|
@@ -1172,11 +1172,11 @@ var notifications_exports = {};
|
|
|
1172
1172
|
__export(notifications_exports, {
|
|
1173
1173
|
generateNotifications: () => generateNotifications
|
|
1174
1174
|
});
|
|
1175
|
-
import
|
|
1176
|
-
import
|
|
1175
|
+
import fs11 from "fs-extra";
|
|
1176
|
+
import path11 from "path";
|
|
1177
1177
|
async function generateNotifications(projectRoot) {
|
|
1178
|
-
const featureDir =
|
|
1179
|
-
if (await
|
|
1178
|
+
const featureDir = path11.join(projectRoot, "src", "features", "notifications");
|
|
1179
|
+
if (await fs11.pathExists(featureDir)) {
|
|
1180
1180
|
log.error("Notifications feature already exists at src/features/notifications/");
|
|
1181
1181
|
return;
|
|
1182
1182
|
}
|
|
@@ -1199,10 +1199,10 @@ async function generateNotifications(projectRoot) {
|
|
|
1199
1199
|
};
|
|
1200
1200
|
let count = 0;
|
|
1201
1201
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1202
|
-
const fullPath =
|
|
1202
|
+
const fullPath = path11.join(projectRoot, filePath);
|
|
1203
1203
|
ctx.trackCreatedFile(fullPath);
|
|
1204
|
-
await
|
|
1205
|
-
await
|
|
1204
|
+
await fs11.ensureDir(path11.dirname(fullPath));
|
|
1205
|
+
await fs11.writeFile(fullPath, content);
|
|
1206
1206
|
count++;
|
|
1207
1207
|
}
|
|
1208
1208
|
await addUserRelation(projectRoot, "notifications Notification[]", ctx);
|
|
@@ -1229,10 +1229,10 @@ async function generateNotifications(projectRoot) {
|
|
|
1229
1229
|
}
|
|
1230
1230
|
}
|
|
1231
1231
|
async function wireProtectedNav2(projectRoot, ctx) {
|
|
1232
|
-
const navPath =
|
|
1233
|
-
if (!await
|
|
1232
|
+
const navPath = path11.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
1233
|
+
if (!await fs11.pathExists(navPath)) return;
|
|
1234
1234
|
await ctx.trackModifiedFile(navPath);
|
|
1235
|
-
let content = await
|
|
1235
|
+
let content = await fs11.readFile(navPath, "utf-8");
|
|
1236
1236
|
if (content.includes("NotificationBell")) return;
|
|
1237
1237
|
content = insertImportAfterDirectives(
|
|
1238
1238
|
content,
|
|
@@ -1243,15 +1243,15 @@ async function wireProtectedNav2(projectRoot, ctx) {
|
|
|
1243
1243
|
/(<div className="flex items-center gap-3">)\s*\n(\s*)(<div className="hidden md:block">)/,
|
|
1244
1244
|
"$1\n$2<NotificationBell />\n$2$3"
|
|
1245
1245
|
);
|
|
1246
|
-
await
|
|
1246
|
+
await fs11.writeFile(navPath, content);
|
|
1247
1247
|
}
|
|
1248
1248
|
async function setConfigFlag3(projectRoot, ctx) {
|
|
1249
|
-
const configPath =
|
|
1250
|
-
if (!await
|
|
1249
|
+
const configPath = path11.join(projectRoot, "src", "config", "app.config.ts");
|
|
1250
|
+
if (!await fs11.pathExists(configPath)) return;
|
|
1251
1251
|
await ctx.trackModifiedFile(configPath);
|
|
1252
|
-
const content = await
|
|
1252
|
+
const content = await fs11.readFile(configPath, "utf-8");
|
|
1253
1253
|
const updated = content.replace(/notifications:\s*false/, "notifications: true");
|
|
1254
|
-
await
|
|
1254
|
+
await fs11.writeFile(configPath, updated);
|
|
1255
1255
|
}
|
|
1256
1256
|
function prismaSchema() {
|
|
1257
1257
|
return `// Generated by mars generate notifications (notifications@${GENERATOR_VERSION3})
|
|
@@ -1847,11 +1847,11 @@ var analytics_exports = {};
|
|
|
1847
1847
|
__export(analytics_exports, {
|
|
1848
1848
|
generateAnalytics: () => generateAnalytics
|
|
1849
1849
|
});
|
|
1850
|
-
import
|
|
1851
|
-
import
|
|
1850
|
+
import fs12 from "fs-extra";
|
|
1851
|
+
import path12 from "path";
|
|
1852
1852
|
async function generateAnalytics(projectRoot) {
|
|
1853
|
-
const featureDir =
|
|
1854
|
-
if (await
|
|
1853
|
+
const featureDir = path12.join(projectRoot, "src", "features", "analytics");
|
|
1854
|
+
if (await fs12.pathExists(featureDir)) {
|
|
1855
1855
|
log.error("Analytics feature already exists at src/features/analytics/");
|
|
1856
1856
|
return;
|
|
1857
1857
|
}
|
|
@@ -1866,10 +1866,10 @@ async function generateAnalytics(projectRoot) {
|
|
|
1866
1866
|
};
|
|
1867
1867
|
let count = 0;
|
|
1868
1868
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1869
|
-
const fullPath =
|
|
1869
|
+
const fullPath = path12.join(projectRoot, filePath);
|
|
1870
1870
|
ctx.trackCreatedFile(fullPath);
|
|
1871
|
-
await
|
|
1872
|
-
await
|
|
1871
|
+
await fs12.ensureDir(path12.dirname(fullPath));
|
|
1872
|
+
await fs12.writeFile(fullPath, content);
|
|
1873
1873
|
count++;
|
|
1874
1874
|
}
|
|
1875
1875
|
await wireProviders2(projectRoot, ctx);
|
|
@@ -1900,10 +1900,10 @@ async function generateAnalytics(projectRoot) {
|
|
|
1900
1900
|
}
|
|
1901
1901
|
}
|
|
1902
1902
|
async function wireProviders2(projectRoot, ctx) {
|
|
1903
|
-
const providersPath =
|
|
1904
|
-
if (!await
|
|
1903
|
+
const providersPath = path12.join(projectRoot, "src", "app", "providers.tsx");
|
|
1904
|
+
if (!await fs12.pathExists(providersPath)) return;
|
|
1905
1905
|
await ctx.trackModifiedFile(providersPath);
|
|
1906
|
-
let content = await
|
|
1906
|
+
let content = await fs12.readFile(providersPath, "utf-8");
|
|
1907
1907
|
if (content.includes("AnalyticsProvider")) return;
|
|
1908
1908
|
content = insertImportAfterDirectives(
|
|
1909
1909
|
content,
|
|
@@ -1918,8 +1918,8 @@ async function wireProviders2(projectRoot, ctx) {
|
|
|
1918
1918
|
);`;
|
|
1919
1919
|
}
|
|
1920
1920
|
);
|
|
1921
|
-
await
|
|
1922
|
-
const written = await
|
|
1921
|
+
await fs12.writeFile(providersPath, content);
|
|
1922
|
+
const written = await fs12.readFile(providersPath, "utf-8");
|
|
1923
1923
|
if (!written.includes("AnalyticsProvider")) {
|
|
1924
1924
|
throw new Error(
|
|
1925
1925
|
"wireProviders: AnalyticsProvider was not inserted into providers.tsx \u2014 the return statement pattern did not match. Review the template file structure."
|
|
@@ -1927,12 +1927,12 @@ async function wireProviders2(projectRoot, ctx) {
|
|
|
1927
1927
|
}
|
|
1928
1928
|
}
|
|
1929
1929
|
async function setConfigFlag4(projectRoot, ctx) {
|
|
1930
|
-
const configPath =
|
|
1931
|
-
if (!await
|
|
1930
|
+
const configPath = path12.join(projectRoot, "src", "config", "app.config.ts");
|
|
1931
|
+
if (!await fs12.pathExists(configPath)) return;
|
|
1932
1932
|
await ctx.trackModifiedFile(configPath);
|
|
1933
|
-
const content = await
|
|
1933
|
+
const content = await fs12.readFile(configPath, "utf-8");
|
|
1934
1934
|
const updated = content.replace(/analytics:\s*false/, "analytics: true");
|
|
1935
|
-
await
|
|
1935
|
+
await fs12.writeFile(configPath, updated);
|
|
1936
1936
|
}
|
|
1937
1937
|
function types3() {
|
|
1938
1938
|
return `${STAMP4}
|
|
@@ -2291,11 +2291,11 @@ var command_palette_exports = {};
|
|
|
2291
2291
|
__export(command_palette_exports, {
|
|
2292
2292
|
generateCommandPalette: () => generateCommandPalette
|
|
2293
2293
|
});
|
|
2294
|
-
import
|
|
2295
|
-
import
|
|
2294
|
+
import fs13 from "fs-extra";
|
|
2295
|
+
import path13 from "path";
|
|
2296
2296
|
async function generateCommandPalette(projectRoot) {
|
|
2297
|
-
const featureDir =
|
|
2298
|
-
if (await
|
|
2297
|
+
const featureDir = path13.join(projectRoot, "src", "features", "command-palette");
|
|
2298
|
+
if (await fs13.pathExists(featureDir)) {
|
|
2299
2299
|
log.error("Command palette feature already exists at src/features/command-palette/");
|
|
2300
2300
|
return;
|
|
2301
2301
|
}
|
|
@@ -2313,10 +2313,10 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2313
2313
|
};
|
|
2314
2314
|
let count = 0;
|
|
2315
2315
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2316
|
-
const fullPath =
|
|
2316
|
+
const fullPath = path13.join(projectRoot, filePath);
|
|
2317
2317
|
ctx.trackCreatedFile(fullPath);
|
|
2318
|
-
await
|
|
2319
|
-
await
|
|
2318
|
+
await fs13.ensureDir(path13.dirname(fullPath));
|
|
2319
|
+
await fs13.writeFile(fullPath, content);
|
|
2320
2320
|
count++;
|
|
2321
2321
|
}
|
|
2322
2322
|
await addDependencies(projectRoot, { cmdk: "^1.0.0" });
|
|
@@ -2339,9 +2339,9 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2339
2339
|
}
|
|
2340
2340
|
}
|
|
2341
2341
|
async function wireLayout2(projectRoot, ctx) {
|
|
2342
|
-
const layoutPath =
|
|
2343
|
-
if (!await
|
|
2344
|
-
let content = await
|
|
2342
|
+
const layoutPath = path13.join(projectRoot, "src", "app", "layout.tsx");
|
|
2343
|
+
if (!await fs13.pathExists(layoutPath)) return;
|
|
2344
|
+
let content = await fs13.readFile(layoutPath, "utf-8");
|
|
2345
2345
|
if (content.includes("CommandPalette")) return;
|
|
2346
2346
|
await ctx.trackModifiedFile(layoutPath);
|
|
2347
2347
|
content = `import { CommandPalette } from '@/features/command-palette';
|
|
@@ -2350,15 +2350,15 @@ ${content}`;
|
|
|
2350
2350
|
/(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
|
|
2351
2351
|
"$1$2\n$1<CommandPalette />"
|
|
2352
2352
|
);
|
|
2353
|
-
await
|
|
2353
|
+
await fs13.writeFile(layoutPath, content);
|
|
2354
2354
|
}
|
|
2355
2355
|
async function setConfigFlag5(projectRoot, ctx) {
|
|
2356
|
-
const configPath =
|
|
2357
|
-
if (!await
|
|
2356
|
+
const configPath = path13.join(projectRoot, "src", "config", "app.config.ts");
|
|
2357
|
+
if (!await fs13.pathExists(configPath)) return;
|
|
2358
2358
|
await ctx.trackModifiedFile(configPath);
|
|
2359
|
-
const content = await
|
|
2359
|
+
const content = await fs13.readFile(configPath, "utf-8");
|
|
2360
2360
|
const updated = content.replace(/commandPalette:\s*false/, "commandPalette: true");
|
|
2361
|
-
await
|
|
2361
|
+
await fs13.writeFile(configPath, updated);
|
|
2362
2362
|
}
|
|
2363
2363
|
function types4() {
|
|
2364
2364
|
return `${STAMP5}
|
|
@@ -2774,11 +2774,11 @@ var onboarding_exports = {};
|
|
|
2774
2774
|
__export(onboarding_exports, {
|
|
2775
2775
|
generateOnboarding: () => generateOnboarding
|
|
2776
2776
|
});
|
|
2777
|
-
import
|
|
2778
|
-
import
|
|
2777
|
+
import fs14 from "fs-extra";
|
|
2778
|
+
import path14 from "path";
|
|
2779
2779
|
async function generateOnboarding(projectRoot) {
|
|
2780
|
-
const featureDir =
|
|
2781
|
-
if (await
|
|
2780
|
+
const featureDir = path14.join(projectRoot, "src", "features", "onboarding");
|
|
2781
|
+
if (await fs14.pathExists(featureDir)) {
|
|
2782
2782
|
log.error("Onboarding feature already exists at src/features/onboarding/");
|
|
2783
2783
|
return;
|
|
2784
2784
|
}
|
|
@@ -2800,10 +2800,10 @@ async function generateOnboarding(projectRoot) {
|
|
|
2800
2800
|
};
|
|
2801
2801
|
let count = 0;
|
|
2802
2802
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2803
|
-
const fullPath =
|
|
2803
|
+
const fullPath = path14.join(projectRoot, filePath);
|
|
2804
2804
|
ctx.trackCreatedFile(fullPath);
|
|
2805
|
-
await
|
|
2806
|
-
await
|
|
2805
|
+
await fs14.ensureDir(path14.dirname(fullPath));
|
|
2806
|
+
await fs14.writeFile(fullPath, content);
|
|
2807
2807
|
count++;
|
|
2808
2808
|
}
|
|
2809
2809
|
await addUserRelation(projectRoot, "onboardingProgress OnboardingProgress?", ctx);
|
|
@@ -2831,7 +2831,7 @@ async function generateOnboarding(projectRoot) {
|
|
|
2831
2831
|
}
|
|
2832
2832
|
}
|
|
2833
2833
|
async function patchDashboardWithOnboardingRedirect(projectRoot, ctx) {
|
|
2834
|
-
const dashboardPath =
|
|
2834
|
+
const dashboardPath = path14.join(
|
|
2835
2835
|
projectRoot,
|
|
2836
2836
|
"src",
|
|
2837
2837
|
"app",
|
|
@@ -2839,9 +2839,9 @@ async function patchDashboardWithOnboardingRedirect(projectRoot, ctx) {
|
|
|
2839
2839
|
"dashboard",
|
|
2840
2840
|
"page.tsx"
|
|
2841
2841
|
);
|
|
2842
|
-
if (!await
|
|
2842
|
+
if (!await fs14.pathExists(dashboardPath)) return;
|
|
2843
2843
|
await ctx.trackModifiedFile(dashboardPath);
|
|
2844
|
-
let content = await
|
|
2844
|
+
let content = await fs14.readFile(dashboardPath, "utf-8");
|
|
2845
2845
|
if (content.includes("isOnboardingComplete")) return;
|
|
2846
2846
|
const redirectImport = `import { redirect } from 'next/navigation';
|
|
2847
2847
|
`;
|
|
@@ -2876,27 +2876,27 @@ async function patchDashboardWithOnboardingRedirect(projectRoot, ctx) {
|
|
|
2876
2876
|
`;
|
|
2877
2877
|
content = content.slice(0, insertAt) + redirectBlock + content.slice(insertAt);
|
|
2878
2878
|
}
|
|
2879
|
-
await
|
|
2879
|
+
await fs14.writeFile(dashboardPath, content);
|
|
2880
2880
|
}
|
|
2881
2881
|
async function patchProxyWithOnboardingRoute(projectRoot, ctx) {
|
|
2882
|
-
const proxyPath =
|
|
2883
|
-
if (!await
|
|
2882
|
+
const proxyPath = path14.join(projectRoot, "src", "proxy.ts");
|
|
2883
|
+
if (!await fs14.pathExists(proxyPath)) return;
|
|
2884
2884
|
await ctx.trackModifiedFile(proxyPath);
|
|
2885
|
-
let content = await
|
|
2885
|
+
let content = await fs14.readFile(proxyPath, "utf-8");
|
|
2886
2886
|
if (content.includes("routes.onboarding")) return;
|
|
2887
2887
|
content = content.replace(
|
|
2888
2888
|
/const protectedRoutes = \[([^\]]*)\]/,
|
|
2889
2889
|
"const protectedRoutes = [$1, routes.onboarding]"
|
|
2890
2890
|
);
|
|
2891
|
-
await
|
|
2891
|
+
await fs14.writeFile(proxyPath, content);
|
|
2892
2892
|
}
|
|
2893
2893
|
async function setConfigFlag6(projectRoot, ctx) {
|
|
2894
|
-
const configPath =
|
|
2895
|
-
if (!await
|
|
2894
|
+
const configPath = path14.join(projectRoot, "src", "config", "app.config.ts");
|
|
2895
|
+
if (!await fs14.pathExists(configPath)) return;
|
|
2896
2896
|
await ctx.trackModifiedFile(configPath);
|
|
2897
|
-
const content = await
|
|
2897
|
+
const content = await fs14.readFile(configPath, "utf-8");
|
|
2898
2898
|
const updated = content.replace(/onboarding:\s*false/, "onboarding: true");
|
|
2899
|
-
await
|
|
2899
|
+
await fs14.writeFile(configPath, updated);
|
|
2900
2900
|
}
|
|
2901
2901
|
function prismaSchema2() {
|
|
2902
2902
|
return `// Generated by mars generate onboarding (onboarding@${GENERATOR_VERSION6})
|
|
@@ -3423,11 +3423,11 @@ var search_exports = {};
|
|
|
3423
3423
|
__export(search_exports, {
|
|
3424
3424
|
generateSearch: () => generateSearch
|
|
3425
3425
|
});
|
|
3426
|
-
import
|
|
3427
|
-
import
|
|
3426
|
+
import fs15 from "fs-extra";
|
|
3427
|
+
import path15 from "path";
|
|
3428
3428
|
async function generateSearch(projectRoot) {
|
|
3429
|
-
const featureDir =
|
|
3430
|
-
if (await
|
|
3429
|
+
const featureDir = path15.join(projectRoot, "src", "features", "search");
|
|
3430
|
+
if (await fs15.pathExists(featureDir)) {
|
|
3431
3431
|
log.error("Search feature already exists at src/features/search/");
|
|
3432
3432
|
return;
|
|
3433
3433
|
}
|
|
@@ -3447,10 +3447,10 @@ async function generateSearch(projectRoot) {
|
|
|
3447
3447
|
};
|
|
3448
3448
|
let count = 0;
|
|
3449
3449
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3450
|
-
const fullPath =
|
|
3450
|
+
const fullPath = path15.join(projectRoot, filePath);
|
|
3451
3451
|
ctx.trackCreatedFile(fullPath);
|
|
3452
|
-
await
|
|
3453
|
-
await
|
|
3452
|
+
await fs15.ensureDir(path15.dirname(fullPath));
|
|
3453
|
+
await fs15.writeFile(fullPath, content);
|
|
3454
3454
|
count++;
|
|
3455
3455
|
}
|
|
3456
3456
|
await wireProtectedNav3(projectRoot, ctx);
|
|
@@ -3478,9 +3478,9 @@ async function generateSearch(projectRoot) {
|
|
|
3478
3478
|
}
|
|
3479
3479
|
}
|
|
3480
3480
|
async function wireProtectedNav3(projectRoot, ctx) {
|
|
3481
|
-
const navPath =
|
|
3482
|
-
if (!await
|
|
3483
|
-
let content = await
|
|
3481
|
+
const navPath = path15.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
3482
|
+
if (!await fs15.pathExists(navPath)) return;
|
|
3483
|
+
let content = await fs15.readFile(navPath, "utf-8");
|
|
3484
3484
|
if (content.includes("NavSearch")) return;
|
|
3485
3485
|
await ctx.trackModifiedFile(navPath);
|
|
3486
3486
|
content = insertImportAfterDirectives(
|
|
@@ -3492,15 +3492,15 @@ async function wireProtectedNav3(projectRoot, ctx) {
|
|
|
3492
3492
|
/(<div className="flex items-center gap-3">)\s*\n(\s*)(<)/,
|
|
3493
3493
|
"$1\n$2<NavSearch />\n$2$3"
|
|
3494
3494
|
);
|
|
3495
|
-
await
|
|
3495
|
+
await fs15.writeFile(navPath, content);
|
|
3496
3496
|
}
|
|
3497
3497
|
async function setConfigFlag7(projectRoot, ctx) {
|
|
3498
|
-
const configPath =
|
|
3499
|
-
if (!await
|
|
3498
|
+
const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
|
|
3499
|
+
if (!await fs15.pathExists(configPath)) return;
|
|
3500
3500
|
await ctx.trackModifiedFile(configPath);
|
|
3501
|
-
const content = await
|
|
3501
|
+
const content = await fs15.readFile(configPath, "utf-8");
|
|
3502
3502
|
const updated = content.replace(/search:\s*false/, "search: true");
|
|
3503
|
-
await
|
|
3503
|
+
await fs15.writeFile(configPath, updated);
|
|
3504
3504
|
}
|
|
3505
3505
|
function types6() {
|
|
3506
3506
|
return `${STAMP7}
|
|
@@ -3854,11 +3854,11 @@ var realtime_exports = {};
|
|
|
3854
3854
|
__export(realtime_exports, {
|
|
3855
3855
|
generateRealtime: () => generateRealtime
|
|
3856
3856
|
});
|
|
3857
|
-
import
|
|
3858
|
-
import
|
|
3857
|
+
import fs16 from "fs-extra";
|
|
3858
|
+
import path16 from "path";
|
|
3859
3859
|
async function generateRealtime(projectRoot) {
|
|
3860
|
-
const featureDir =
|
|
3861
|
-
if (await
|
|
3860
|
+
const featureDir = path16.join(projectRoot, "src", "features", "realtime");
|
|
3861
|
+
if (await fs16.pathExists(featureDir)) {
|
|
3862
3862
|
log.error("Realtime feature already exists at src/features/realtime/");
|
|
3863
3863
|
return;
|
|
3864
3864
|
}
|
|
@@ -3875,10 +3875,10 @@ async function generateRealtime(projectRoot) {
|
|
|
3875
3875
|
};
|
|
3876
3876
|
let count = 0;
|
|
3877
3877
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3878
|
-
const fullPath =
|
|
3878
|
+
const fullPath = path16.join(projectRoot, filePath);
|
|
3879
3879
|
ctx.trackCreatedFile(fullPath);
|
|
3880
|
-
await
|
|
3881
|
-
await
|
|
3880
|
+
await fs16.ensureDir(path16.dirname(fullPath));
|
|
3881
|
+
await fs16.writeFile(fullPath, content);
|
|
3882
3882
|
count++;
|
|
3883
3883
|
}
|
|
3884
3884
|
await setConfigFlag8(projectRoot, ctx);
|
|
@@ -3903,12 +3903,12 @@ async function generateRealtime(projectRoot) {
|
|
|
3903
3903
|
}
|
|
3904
3904
|
}
|
|
3905
3905
|
async function setConfigFlag8(projectRoot, ctx) {
|
|
3906
|
-
const configPath =
|
|
3907
|
-
if (!await
|
|
3906
|
+
const configPath = path16.join(projectRoot, "src", "config", "app.config.ts");
|
|
3907
|
+
if (!await fs16.pathExists(configPath)) return;
|
|
3908
3908
|
await ctx.trackModifiedFile(configPath);
|
|
3909
|
-
const content = await
|
|
3909
|
+
const content = await fs16.readFile(configPath, "utf-8");
|
|
3910
3910
|
const updated = content.replace(/realtime:\s*false/, "realtime: true");
|
|
3911
|
-
await
|
|
3911
|
+
await fs16.writeFile(configPath, updated);
|
|
3912
3912
|
}
|
|
3913
3913
|
function types7() {
|
|
3914
3914
|
return `${STAMP8}
|
|
@@ -4217,11 +4217,11 @@ var ai_exports = {};
|
|
|
4217
4217
|
__export(ai_exports, {
|
|
4218
4218
|
generateAI: () => generateAI
|
|
4219
4219
|
});
|
|
4220
|
-
import
|
|
4221
|
-
import
|
|
4220
|
+
import fs17 from "fs-extra";
|
|
4221
|
+
import path17 from "path";
|
|
4222
4222
|
async function generateAI(projectRoot) {
|
|
4223
|
-
const featureDir =
|
|
4224
|
-
if (await
|
|
4223
|
+
const featureDir = path17.join(projectRoot, "src", "features", "ai");
|
|
4224
|
+
if (await fs17.pathExists(featureDir)) {
|
|
4225
4225
|
log.error("AI feature already exists at src/features/ai/");
|
|
4226
4226
|
return;
|
|
4227
4227
|
}
|
|
@@ -4244,10 +4244,10 @@ async function generateAI(projectRoot) {
|
|
|
4244
4244
|
};
|
|
4245
4245
|
let count = 0;
|
|
4246
4246
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4247
|
-
const fullPath =
|
|
4247
|
+
const fullPath = path17.join(projectRoot, filePath);
|
|
4248
4248
|
ctx.trackCreatedFile(fullPath);
|
|
4249
|
-
await
|
|
4250
|
-
await
|
|
4249
|
+
await fs17.ensureDir(path17.dirname(fullPath));
|
|
4250
|
+
await fs17.writeFile(fullPath, content);
|
|
4251
4251
|
count++;
|
|
4252
4252
|
}
|
|
4253
4253
|
await addDependencies(projectRoot, {
|
|
@@ -4275,12 +4275,12 @@ async function generateAI(projectRoot) {
|
|
|
4275
4275
|
}
|
|
4276
4276
|
}
|
|
4277
4277
|
async function setConfigFlag9(projectRoot, ctx) {
|
|
4278
|
-
const configPath =
|
|
4279
|
-
if (!await
|
|
4278
|
+
const configPath = path17.join(projectRoot, "src", "config", "app.config.ts");
|
|
4279
|
+
if (!await fs17.pathExists(configPath)) return;
|
|
4280
4280
|
await ctx.trackModifiedFile(configPath);
|
|
4281
|
-
const content = await
|
|
4281
|
+
const content = await fs17.readFile(configPath, "utf-8");
|
|
4282
4282
|
const updated = content.replace(/ai:\s*false/, "ai: true");
|
|
4283
|
-
await
|
|
4283
|
+
await fs17.writeFile(configPath, updated);
|
|
4284
4284
|
}
|
|
4285
4285
|
function types8() {
|
|
4286
4286
|
return `${STAMP9}
|
|
@@ -4875,15 +4875,15 @@ export { ChatInput } from './ChatInput';
|
|
|
4875
4875
|
`;
|
|
4876
4876
|
}
|
|
4877
4877
|
async function wireProtectedNav4(projectRoot, ctx) {
|
|
4878
|
-
const layoutPath =
|
|
4878
|
+
const layoutPath = path17.join(
|
|
4879
4879
|
projectRoot,
|
|
4880
4880
|
"src",
|
|
4881
4881
|
"app",
|
|
4882
4882
|
"(protected)",
|
|
4883
4883
|
"layout.tsx"
|
|
4884
4884
|
);
|
|
4885
|
-
if (!await
|
|
4886
|
-
let content = await
|
|
4885
|
+
if (!await fs17.pathExists(layoutPath)) return;
|
|
4886
|
+
let content = await fs17.readFile(layoutPath, "utf-8");
|
|
4887
4887
|
if (content.includes("routes.ai") || content.includes("label: 'AI'")) return;
|
|
4888
4888
|
await ctx.trackModifiedFile(layoutPath);
|
|
4889
4889
|
const insertion = ` if (appConfig.features.ai) {
|
|
@@ -4902,7 +4902,7 @@ async function wireProtectedNav4(projectRoot, ctx) {
|
|
|
4902
4902
|
"// AI nav link added by mars generate ai\n"
|
|
4903
4903
|
);
|
|
4904
4904
|
}
|
|
4905
|
-
await
|
|
4905
|
+
await fs17.writeFile(layoutPath, content);
|
|
4906
4906
|
}
|
|
4907
4907
|
function aiPage() {
|
|
4908
4908
|
return `${STAMP9}
|
|
@@ -4991,11 +4991,11 @@ var cookie_consent_exports = {};
|
|
|
4991
4991
|
__export(cookie_consent_exports, {
|
|
4992
4992
|
generateCookieConsent: () => generateCookieConsent
|
|
4993
4993
|
});
|
|
4994
|
-
import
|
|
4995
|
-
import
|
|
4994
|
+
import fs18 from "fs-extra";
|
|
4995
|
+
import path18 from "path";
|
|
4996
4996
|
async function generateCookieConsent(projectRoot) {
|
|
4997
|
-
const featureDir =
|
|
4998
|
-
if (await
|
|
4997
|
+
const featureDir = path18.join(projectRoot, "src", "features", "cookie-consent");
|
|
4998
|
+
if (await fs18.pathExists(featureDir)) {
|
|
4999
4999
|
log.error("Cookie consent feature already exists at src/features/cookie-consent/");
|
|
5000
5000
|
return;
|
|
5001
5001
|
}
|
|
@@ -5011,10 +5011,10 @@ async function generateCookieConsent(projectRoot) {
|
|
|
5011
5011
|
};
|
|
5012
5012
|
let count = 0;
|
|
5013
5013
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5014
|
-
const fullPath =
|
|
5014
|
+
const fullPath = path18.join(projectRoot, filePath);
|
|
5015
5015
|
ctx.trackCreatedFile(fullPath);
|
|
5016
|
-
await
|
|
5017
|
-
await
|
|
5016
|
+
await fs18.ensureDir(path18.dirname(fullPath));
|
|
5017
|
+
await fs18.writeFile(fullPath, content);
|
|
5018
5018
|
count++;
|
|
5019
5019
|
}
|
|
5020
5020
|
await wireLayout3(projectRoot, ctx);
|
|
@@ -5039,9 +5039,9 @@ async function generateCookieConsent(projectRoot) {
|
|
|
5039
5039
|
}
|
|
5040
5040
|
}
|
|
5041
5041
|
async function wireLayout3(projectRoot, ctx) {
|
|
5042
|
-
const layoutPath =
|
|
5043
|
-
if (!await
|
|
5044
|
-
let content = await
|
|
5042
|
+
const layoutPath = path18.join(projectRoot, "src", "app", "layout.tsx");
|
|
5043
|
+
if (!await fs18.pathExists(layoutPath)) return;
|
|
5044
|
+
let content = await fs18.readFile(layoutPath, "utf-8");
|
|
5045
5045
|
if (content.includes("CookieConsentBanner")) return;
|
|
5046
5046
|
await ctx.trackModifiedFile(layoutPath);
|
|
5047
5047
|
content = `import { CookieConsentBanner } from '@/features/cookie-consent';
|
|
@@ -5050,15 +5050,15 @@ ${content}`;
|
|
|
5050
5050
|
/(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
|
|
5051
5051
|
"$1$2\n$1<CookieConsentBanner />"
|
|
5052
5052
|
);
|
|
5053
|
-
await
|
|
5053
|
+
await fs18.writeFile(layoutPath, content);
|
|
5054
5054
|
}
|
|
5055
5055
|
async function setConfigFlag10(projectRoot, ctx) {
|
|
5056
|
-
const configPath =
|
|
5057
|
-
if (!await
|
|
5056
|
+
const configPath = path18.join(projectRoot, "src", "config", "app.config.ts");
|
|
5057
|
+
if (!await fs18.pathExists(configPath)) return;
|
|
5058
5058
|
await ctx.trackModifiedFile(configPath);
|
|
5059
|
-
const content = await
|
|
5059
|
+
const content = await fs18.readFile(configPath, "utf-8");
|
|
5060
5060
|
const updated = content.replace(/cookieConsent:\s*false/, "cookieConsent: true");
|
|
5061
|
-
await
|
|
5061
|
+
await fs18.writeFile(configPath, updated);
|
|
5062
5062
|
}
|
|
5063
5063
|
function types9() {
|
|
5064
5064
|
return `${STAMP10}
|
|
@@ -5343,11 +5343,11 @@ var coming_soon_exports = {};
|
|
|
5343
5343
|
__export(coming_soon_exports, {
|
|
5344
5344
|
generateComingSoon: () => generateComingSoon
|
|
5345
5345
|
});
|
|
5346
|
-
import
|
|
5347
|
-
import
|
|
5346
|
+
import fs19 from "fs-extra";
|
|
5347
|
+
import path19 from "path";
|
|
5348
5348
|
async function generateComingSoon(projectRoot) {
|
|
5349
|
-
const featureDir =
|
|
5350
|
-
if (await
|
|
5349
|
+
const featureDir = path19.join(projectRoot, "src", "features", "coming-soon");
|
|
5350
|
+
if (await fs19.pathExists(featureDir)) {
|
|
5351
5351
|
log.error("Coming soon feature already exists at src/features/coming-soon/");
|
|
5352
5352
|
return;
|
|
5353
5353
|
}
|
|
@@ -5364,10 +5364,10 @@ async function generateComingSoon(projectRoot) {
|
|
|
5364
5364
|
};
|
|
5365
5365
|
let count = 0;
|
|
5366
5366
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5367
|
-
const fullPath =
|
|
5367
|
+
const fullPath = path19.join(projectRoot, filePath);
|
|
5368
5368
|
ctx.trackCreatedFile(fullPath);
|
|
5369
|
-
await
|
|
5370
|
-
await
|
|
5369
|
+
await fs19.ensureDir(path19.dirname(fullPath));
|
|
5370
|
+
await fs19.writeFile(fullPath, content);
|
|
5371
5371
|
count++;
|
|
5372
5372
|
}
|
|
5373
5373
|
await registerRoute(projectRoot, "comingSoon", "/coming-soon", ctx);
|
|
@@ -5389,12 +5389,12 @@ async function generateComingSoon(projectRoot) {
|
|
|
5389
5389
|
}
|
|
5390
5390
|
}
|
|
5391
5391
|
async function setConfigFlag11(projectRoot, ctx) {
|
|
5392
|
-
const configPath =
|
|
5393
|
-
if (!await
|
|
5392
|
+
const configPath = path19.join(projectRoot, "src", "config", "app.config.ts");
|
|
5393
|
+
if (!await fs19.pathExists(configPath)) return;
|
|
5394
5394
|
await ctx.trackModifiedFile(configPath);
|
|
5395
|
-
const content = await
|
|
5395
|
+
const content = await fs19.readFile(configPath, "utf-8");
|
|
5396
5396
|
const updated = content.replace(/comingSoon:\s*false/, "comingSoon: true");
|
|
5397
|
-
await
|
|
5397
|
+
await fs19.writeFile(configPath, updated);
|
|
5398
5398
|
}
|
|
5399
5399
|
function types10() {
|
|
5400
5400
|
return `${STAMP11}
|
|
@@ -5582,11 +5582,11 @@ var sentry_exports = {};
|
|
|
5582
5582
|
__export(sentry_exports, {
|
|
5583
5583
|
generateSentry: () => generateSentry
|
|
5584
5584
|
});
|
|
5585
|
-
import
|
|
5586
|
-
import
|
|
5585
|
+
import fs20 from "fs-extra";
|
|
5586
|
+
import path20 from "path";
|
|
5587
5587
|
async function generateSentry(projectRoot) {
|
|
5588
|
-
const featureDir =
|
|
5589
|
-
if (await
|
|
5588
|
+
const featureDir = path20.join(projectRoot, "src", "features", "sentry");
|
|
5589
|
+
if (await fs20.pathExists(featureDir)) {
|
|
5590
5590
|
log.error("Sentry feature already exists at src/features/sentry/");
|
|
5591
5591
|
return;
|
|
5592
5592
|
}
|
|
@@ -5602,10 +5602,10 @@ async function generateSentry(projectRoot) {
|
|
|
5602
5602
|
};
|
|
5603
5603
|
let count = 0;
|
|
5604
5604
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5605
|
-
const fullPath =
|
|
5605
|
+
const fullPath = path20.join(projectRoot, filePath);
|
|
5606
5606
|
ctx.trackCreatedFile(fullPath);
|
|
5607
|
-
await
|
|
5608
|
-
await
|
|
5607
|
+
await fs20.ensureDir(path20.dirname(fullPath));
|
|
5608
|
+
await fs20.writeFile(fullPath, content);
|
|
5609
5609
|
count++;
|
|
5610
5610
|
}
|
|
5611
5611
|
await addDependencies(projectRoot, { "@sentry/nextjs": "^8.0.0" });
|
|
@@ -5631,9 +5631,9 @@ async function generateSentry(projectRoot) {
|
|
|
5631
5631
|
}
|
|
5632
5632
|
}
|
|
5633
5633
|
async function wireRootLayout(projectRoot, ctx) {
|
|
5634
|
-
const layoutPath =
|
|
5635
|
-
if (!await
|
|
5636
|
-
let content = await
|
|
5634
|
+
const layoutPath = path20.join(projectRoot, "src", "app", "layout.tsx");
|
|
5635
|
+
if (!await fs20.pathExists(layoutPath)) return;
|
|
5636
|
+
let content = await fs20.readFile(layoutPath, "utf-8");
|
|
5637
5637
|
if (content.includes("ErrorBoundary")) return;
|
|
5638
5638
|
await ctx.trackModifiedFile(layoutPath);
|
|
5639
5639
|
content = `import { ErrorBoundary } from '@/features/sentry';
|
|
@@ -5642,15 +5642,15 @@ ${content}`;
|
|
|
5642
5642
|
/(<Providers>)\{children\}(<\/Providers>)/,
|
|
5643
5643
|
"$1<ErrorBoundary>{children}</ErrorBoundary>$2"
|
|
5644
5644
|
);
|
|
5645
|
-
await
|
|
5645
|
+
await fs20.writeFile(layoutPath, content);
|
|
5646
5646
|
}
|
|
5647
5647
|
async function setConfigFlag12(projectRoot, ctx) {
|
|
5648
|
-
const configPath =
|
|
5649
|
-
if (!await
|
|
5648
|
+
const configPath = path20.join(projectRoot, "src", "config", "app.config.ts");
|
|
5649
|
+
if (!await fs20.pathExists(configPath)) return;
|
|
5650
5650
|
await ctx.trackModifiedFile(configPath);
|
|
5651
|
-
const content = await
|
|
5651
|
+
const content = await fs20.readFile(configPath, "utf-8");
|
|
5652
5652
|
const updated = content.replace(/sentry:\s*false/, "sentry: true");
|
|
5653
|
-
await
|
|
5653
|
+
await fs20.writeFile(configPath, updated);
|
|
5654
5654
|
}
|
|
5655
5655
|
function types11() {
|
|
5656
5656
|
return `${STAMP12}
|
|
@@ -5795,11 +5795,11 @@ var feature_flags_exports = {};
|
|
|
5795
5795
|
__export(feature_flags_exports, {
|
|
5796
5796
|
generateFeatureFlags: () => generateFeatureFlags
|
|
5797
5797
|
});
|
|
5798
|
-
import
|
|
5799
|
-
import
|
|
5798
|
+
import fs21 from "fs-extra";
|
|
5799
|
+
import path21 from "path";
|
|
5800
5800
|
async function generateFeatureFlags(projectRoot) {
|
|
5801
|
-
const featureDir =
|
|
5802
|
-
if (await
|
|
5801
|
+
const featureDir = path21.join(projectRoot, "src", "features", "feature-flags");
|
|
5802
|
+
if (await fs21.pathExists(featureDir)) {
|
|
5803
5803
|
log.error("Feature flags feature already exists at src/features/feature-flags/");
|
|
5804
5804
|
return;
|
|
5805
5805
|
}
|
|
@@ -5818,10 +5818,10 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5818
5818
|
};
|
|
5819
5819
|
let count = 0;
|
|
5820
5820
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5821
|
-
const fullPath =
|
|
5821
|
+
const fullPath = path21.join(projectRoot, filePath);
|
|
5822
5822
|
ctx.trackCreatedFile(fullPath);
|
|
5823
|
-
await
|
|
5824
|
-
await
|
|
5823
|
+
await fs21.ensureDir(path21.dirname(fullPath));
|
|
5824
|
+
await fs21.writeFile(fullPath, content);
|
|
5825
5825
|
count++;
|
|
5826
5826
|
}
|
|
5827
5827
|
await setConfigFlag13(projectRoot, ctx);
|
|
@@ -5841,12 +5841,12 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5841
5841
|
}
|
|
5842
5842
|
}
|
|
5843
5843
|
async function setConfigFlag13(projectRoot, ctx) {
|
|
5844
|
-
const configPath =
|
|
5845
|
-
if (!await
|
|
5844
|
+
const configPath = path21.join(projectRoot, "src", "config", "app.config.ts");
|
|
5845
|
+
if (!await fs21.pathExists(configPath)) return;
|
|
5846
5846
|
await ctx.trackModifiedFile(configPath);
|
|
5847
|
-
const content = await
|
|
5847
|
+
const content = await fs21.readFile(configPath, "utf-8");
|
|
5848
5848
|
const updated = content.replace(/featureFlags:\s*false/, "featureFlags: true");
|
|
5849
|
-
await
|
|
5849
|
+
await fs21.writeFile(configPath, updated);
|
|
5850
5850
|
}
|
|
5851
5851
|
function types12() {
|
|
5852
5852
|
return `${STAMP13}
|
|
@@ -6113,8 +6113,8 @@ function getCliVersion() {
|
|
|
6113
6113
|
|
|
6114
6114
|
// src/commands/create.ts
|
|
6115
6115
|
init_logger();
|
|
6116
|
-
import
|
|
6117
|
-
import
|
|
6116
|
+
import fs23 from "fs-extra";
|
|
6117
|
+
import path23 from "path";
|
|
6118
6118
|
import os3 from "os";
|
|
6119
6119
|
import ora from "ora";
|
|
6120
6120
|
import pc2 from "picocolors";
|
|
@@ -6540,9 +6540,50 @@ async function promptTheme() {
|
|
|
6540
6540
|
|
|
6541
6541
|
// src/generators/scaffold.ts
|
|
6542
6542
|
init_logger();
|
|
6543
|
+
import fs4 from "fs-extra";
|
|
6544
|
+
import path4 from "path";
|
|
6545
|
+
import { generateBrandCss } from "@mars-stack/ui/utils";
|
|
6546
|
+
|
|
6547
|
+
// src/utils/fingerprint.ts
|
|
6548
|
+
import { createHash } from "crypto";
|
|
6543
6549
|
import fs3 from "fs-extra";
|
|
6544
6550
|
import path3 from "path";
|
|
6545
|
-
|
|
6551
|
+
init_logger();
|
|
6552
|
+
async function checksumFile(filePath) {
|
|
6553
|
+
const content = await fs3.readFile(filePath);
|
|
6554
|
+
return createHash("sha256").update(content).digest("hex");
|
|
6555
|
+
}
|
|
6556
|
+
var PLUMBING_FILES_TO_CHECKSUM = [
|
|
6557
|
+
"scripts/ensure-db.mjs",
|
|
6558
|
+
"src/middleware.ts",
|
|
6559
|
+
"src/app/(auth)/sign-in/page.tsx",
|
|
6560
|
+
"src/app/(auth)/register/page.tsx",
|
|
6561
|
+
"src/app/(auth)/verify/page.tsx",
|
|
6562
|
+
"src/lib/mars.ts"
|
|
6563
|
+
];
|
|
6564
|
+
async function writeScaffoldFingerprint(projectDir) {
|
|
6565
|
+
try {
|
|
6566
|
+
const checksums = {};
|
|
6567
|
+
for (const relativePath of PLUMBING_FILES_TO_CHECKSUM) {
|
|
6568
|
+
const fullPath = path3.join(projectDir, relativePath);
|
|
6569
|
+
if (await fs3.pathExists(fullPath)) {
|
|
6570
|
+
checksums[relativePath] = await checksumFile(fullPath);
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
const fingerprint = {
|
|
6574
|
+
schemaVersion: 1,
|
|
6575
|
+
cliVersion: getCliVersion(),
|
|
6576
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6577
|
+
template: "default",
|
|
6578
|
+
checksums
|
|
6579
|
+
};
|
|
6580
|
+
const marsDir = path3.join(projectDir, ".mars");
|
|
6581
|
+
await fs3.ensureDir(marsDir);
|
|
6582
|
+
await fs3.writeJson(path3.join(marsDir, "scaffold.json"), fingerprint, { spaces: 2 });
|
|
6583
|
+
} catch (error) {
|
|
6584
|
+
log.warn("Could not write .mars/scaffold.json \u2014 scaffold continues without fingerprint.");
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6546
6587
|
|
|
6547
6588
|
// src/generators/app-config.ts
|
|
6548
6589
|
function escapeQuotes(value) {
|
|
@@ -6725,17 +6766,17 @@ var IGNORED_FILES = [
|
|
|
6725
6766
|
async function copyTemplateFiles(templateDir, targetDir) {
|
|
6726
6767
|
let fileCount = 0;
|
|
6727
6768
|
async function walkAndCopy(src, dest) {
|
|
6728
|
-
const entries = await
|
|
6769
|
+
const entries = await fs4.readdir(src, { withFileTypes: true });
|
|
6729
6770
|
for (const entry of entries) {
|
|
6730
|
-
const srcPath =
|
|
6731
|
-
const destPath =
|
|
6771
|
+
const srcPath = path4.join(src, entry.name);
|
|
6772
|
+
const destPath = path4.join(dest, entry.name);
|
|
6732
6773
|
if (entry.isDirectory()) {
|
|
6733
6774
|
if (IGNORED_DIRS.includes(entry.name)) continue;
|
|
6734
|
-
await
|
|
6775
|
+
await fs4.ensureDir(destPath);
|
|
6735
6776
|
await walkAndCopy(srcPath, destPath);
|
|
6736
6777
|
} else {
|
|
6737
6778
|
if (IGNORED_FILES.includes(entry.name)) continue;
|
|
6738
|
-
await
|
|
6779
|
+
await fs4.copy(srcPath, destPath);
|
|
6739
6780
|
fileCount++;
|
|
6740
6781
|
}
|
|
6741
6782
|
}
|
|
@@ -6746,19 +6787,19 @@ async function copyTemplateFiles(templateDir, targetDir) {
|
|
|
6746
6787
|
function resolveMarsPackageRange(packageName, templateDir) {
|
|
6747
6788
|
const shortName = packageName.replace("@mars-stack/", "");
|
|
6748
6789
|
const candidates = [
|
|
6749
|
-
|
|
6750
|
-
|
|
6790
|
+
path4.resolve(templateDir, "..", "packages", shortName, "package.json"),
|
|
6791
|
+
path4.resolve(templateDir, "..", "..", "packages", shortName, "package.json")
|
|
6751
6792
|
];
|
|
6752
6793
|
for (const candidate of candidates) {
|
|
6753
|
-
if (
|
|
6754
|
-
const pkg =
|
|
6794
|
+
if (fs4.existsSync(candidate)) {
|
|
6795
|
+
const pkg = fs4.readJsonSync(candidate);
|
|
6755
6796
|
if (typeof pkg.version === "string") {
|
|
6756
6797
|
const [major, minor] = pkg.version.split(".");
|
|
6757
6798
|
return `^${major}.${minor}.0`;
|
|
6758
6799
|
}
|
|
6759
6800
|
}
|
|
6760
6801
|
}
|
|
6761
|
-
const templatePkg =
|
|
6802
|
+
const templatePkg = fs4.readJsonSync(path4.join(templateDir, "package.json"));
|
|
6762
6803
|
const existing = templatePkg.dependencies?.[packageName];
|
|
6763
6804
|
if (existing && existing !== "*") {
|
|
6764
6805
|
if (/^[\^~>=]/.test(existing)) return existing;
|
|
@@ -6769,8 +6810,8 @@ function resolveMarsPackageRange(packageName, templateDir) {
|
|
|
6769
6810
|
}
|
|
6770
6811
|
function generatePackageJson(config) {
|
|
6771
6812
|
const templateDir = getTemplatePath();
|
|
6772
|
-
const templatePkg =
|
|
6773
|
-
const pkg =
|
|
6813
|
+
const templatePkg = path4.join(templateDir, "package.json");
|
|
6814
|
+
const pkg = fs4.readJsonSync(templatePkg);
|
|
6774
6815
|
pkg.name = config.name;
|
|
6775
6816
|
pkg.version = "0.1.0";
|
|
6776
6817
|
pkg.private = true;
|
|
@@ -6920,8 +6961,9 @@ dist/
|
|
|
6920
6961
|
.env.development.local
|
|
6921
6962
|
.env.test
|
|
6922
6963
|
|
|
6923
|
-
# local database
|
|
6924
|
-
.mars
|
|
6964
|
+
# local database (keep scaffold.json for project metadata)
|
|
6965
|
+
.mars/*
|
|
6966
|
+
!.mars/scaffold.json
|
|
6925
6967
|
|
|
6926
6968
|
# misc
|
|
6927
6969
|
.DS_Store
|
|
@@ -6971,9 +7013,9 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6971
7013
|
const flagValue = features[feature];
|
|
6972
7014
|
if (flagValue !== false) continue;
|
|
6973
7015
|
for (const relativePath of paths) {
|
|
6974
|
-
const fullPath =
|
|
6975
|
-
if (await
|
|
6976
|
-
await
|
|
7016
|
+
const fullPath = path4.join(targetDir, relativePath);
|
|
7017
|
+
if (await fs4.pathExists(fullPath)) {
|
|
7018
|
+
await fs4.remove(fullPath);
|
|
6977
7019
|
log.step(`Pruned disabled feature path: ${relativePath}`);
|
|
6978
7020
|
}
|
|
6979
7021
|
}
|
|
@@ -6982,43 +7024,44 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6982
7024
|
}
|
|
6983
7025
|
}
|
|
6984
7026
|
if (relationsToRemove.length > 0) {
|
|
6985
|
-
const authPath =
|
|
6986
|
-
if (await
|
|
6987
|
-
let content = await
|
|
7027
|
+
const authPath = path4.join(targetDir, "prisma", "schema", "auth.prisma");
|
|
7028
|
+
if (await fs4.pathExists(authPath)) {
|
|
7029
|
+
let content = await fs4.readFile(authPath, "utf-8");
|
|
6988
7030
|
for (const field of relationsToRemove) {
|
|
6989
7031
|
content = content.replace(new RegExp(`\\s*${field}\\s+\\S+.*\\n`, "g"), "\n");
|
|
6990
7032
|
}
|
|
6991
|
-
await
|
|
7033
|
+
await fs4.writeFile(authPath, content);
|
|
6992
7034
|
}
|
|
6993
7035
|
}
|
|
6994
7036
|
}
|
|
6995
7037
|
async function scaffoldProject(targetDir, config) {
|
|
6996
7038
|
const templateDir = getTemplatePath();
|
|
6997
|
-
if (!await
|
|
7039
|
+
if (!await fs4.pathExists(templateDir)) {
|
|
6998
7040
|
throw new Error(`Template directory not found: ${templateDir}`);
|
|
6999
7041
|
}
|
|
7000
|
-
await
|
|
7042
|
+
await fs4.ensureDir(targetDir);
|
|
7001
7043
|
const fileCount = await copyTemplateFiles(templateDir, targetDir);
|
|
7002
7044
|
await pruneDisabledFeatures(config.features, targetDir);
|
|
7003
|
-
await
|
|
7004
|
-
await
|
|
7005
|
-
|
|
7045
|
+
await fs4.writeFile(path4.join(targetDir, "package.json"), generatePackageJson(config));
|
|
7046
|
+
await fs4.writeFile(
|
|
7047
|
+
path4.join(targetDir, "src", "config", "app.config.ts"),
|
|
7006
7048
|
generateAppConfig(config)
|
|
7007
7049
|
);
|
|
7008
|
-
await
|
|
7009
|
-
await
|
|
7010
|
-
await
|
|
7011
|
-
await
|
|
7012
|
-
await
|
|
7050
|
+
await fs4.writeFile(path4.join(targetDir, "docker-compose.yml"), generateDockerCompose(config));
|
|
7051
|
+
await fs4.writeFile(path4.join(targetDir, "src", "app", "layout.tsx"), generateLayout(config));
|
|
7052
|
+
await fs4.writeFile(path4.join(targetDir, ".env"), generateEnvFile(config));
|
|
7053
|
+
await fs4.writeFile(path4.join(targetDir, ".env.example"), generateEnvExample(config));
|
|
7054
|
+
await fs4.writeFile(path4.join(targetDir, ".gitignore"), generateGitignore());
|
|
7013
7055
|
await patchGlobalsCssForScaffoldedProject(targetDir, config);
|
|
7014
7056
|
await generateBrandCssForProject(targetDir, config);
|
|
7057
|
+
await writeScaffoldFingerprint(targetDir);
|
|
7015
7058
|
return { fileCount };
|
|
7016
7059
|
}
|
|
7017
7060
|
async function generateBrandCssForProject(targetDir, config) {
|
|
7018
|
-
const brandPath =
|
|
7019
|
-
if (!await
|
|
7061
|
+
const brandPath = path4.join(targetDir, "src", "styles", "brand.css");
|
|
7062
|
+
if (!await fs4.pathExists(brandPath)) return;
|
|
7020
7063
|
const css = generateBrandCss(config.theme.primaryColor);
|
|
7021
|
-
await
|
|
7064
|
+
await fs4.writeFile(brandPath, css);
|
|
7022
7065
|
}
|
|
7023
7066
|
var VALID_DESIGN_DIRECTIONS = [
|
|
7024
7067
|
"modern-saas",
|
|
@@ -7028,9 +7071,9 @@ var VALID_DESIGN_DIRECTIONS = [
|
|
|
7028
7071
|
"dashboard"
|
|
7029
7072
|
];
|
|
7030
7073
|
async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
7031
|
-
const globalsPath =
|
|
7032
|
-
if (!await
|
|
7033
|
-
let content = await
|
|
7074
|
+
const globalsPath = path4.join(targetDir, "src", "styles", "globals.css");
|
|
7075
|
+
if (!await fs4.pathExists(globalsPath)) return;
|
|
7076
|
+
let content = await fs4.readFile(globalsPath, "utf-8");
|
|
7034
7077
|
content = content.replace(
|
|
7035
7078
|
'@source "../../../node_modules/@mars-stack/ui/dist/**/*.js";',
|
|
7036
7079
|
'@source "../../node_modules/@mars-stack/ui/dist/**/*.js";'
|
|
@@ -7049,7 +7092,7 @@ async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
|
7049
7092
|
${newImport}`
|
|
7050
7093
|
);
|
|
7051
7094
|
}
|
|
7052
|
-
await
|
|
7095
|
+
await fs4.writeFile(globalsPath, content);
|
|
7053
7096
|
}
|
|
7054
7097
|
|
|
7055
7098
|
// src/generators/generate-selected.ts
|
|
@@ -7165,13 +7208,13 @@ async function generateSelectedFeatures(projectRoot, features) {
|
|
|
7165
7208
|
}
|
|
7166
7209
|
|
|
7167
7210
|
// src/utils/telemetry.ts
|
|
7168
|
-
import
|
|
7169
|
-
import
|
|
7211
|
+
import fs22 from "fs-extra";
|
|
7212
|
+
import path22 from "path";
|
|
7170
7213
|
import os2 from "os";
|
|
7171
|
-
var RC_PATH =
|
|
7214
|
+
var RC_PATH = path22.join(os2.homedir(), ".marsrc");
|
|
7172
7215
|
function isTelemetryEnabled() {
|
|
7173
7216
|
try {
|
|
7174
|
-
const config =
|
|
7217
|
+
const config = fs22.readJsonSync(RC_PATH);
|
|
7175
7218
|
return config.enabled === true;
|
|
7176
7219
|
} catch {
|
|
7177
7220
|
return false;
|
|
@@ -7180,16 +7223,16 @@ function isTelemetryEnabled() {
|
|
|
7180
7223
|
function enableTelemetry() {
|
|
7181
7224
|
const config = loadOrCreateConfig();
|
|
7182
7225
|
config.enabled = true;
|
|
7183
|
-
|
|
7226
|
+
fs22.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
7184
7227
|
}
|
|
7185
7228
|
function disableTelemetry() {
|
|
7186
7229
|
const config = loadOrCreateConfig();
|
|
7187
7230
|
config.enabled = false;
|
|
7188
|
-
|
|
7231
|
+
fs22.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
7189
7232
|
}
|
|
7190
7233
|
function loadOrCreateConfig() {
|
|
7191
7234
|
try {
|
|
7192
|
-
return
|
|
7235
|
+
return fs22.readJsonSync(RC_PATH);
|
|
7193
7236
|
} catch {
|
|
7194
7237
|
return { enabled: false, anonymousId: crypto.randomUUID() };
|
|
7195
7238
|
}
|
|
@@ -7209,13 +7252,13 @@ function trackEvent(event, properties) {
|
|
|
7209
7252
|
},
|
|
7210
7253
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7211
7254
|
};
|
|
7212
|
-
const logPath =
|
|
7213
|
-
|
|
7255
|
+
const logPath = path22.join(os2.homedir(), ".mars-telemetry.log");
|
|
7256
|
+
fs22.appendFile(logPath, JSON.stringify(payload) + "\n").catch(() => {
|
|
7214
7257
|
});
|
|
7215
7258
|
}
|
|
7216
7259
|
|
|
7217
7260
|
// src/commands/create.ts
|
|
7218
|
-
var RC_PATH2 =
|
|
7261
|
+
var RC_PATH2 = path23.join(os3.homedir(), ".marsrc");
|
|
7219
7262
|
var PROJECT_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
7220
7263
|
var MAX_PROJECT_NAME_LENGTH = 214;
|
|
7221
7264
|
async function createCommand(projectName, options) {
|
|
@@ -7242,8 +7285,8 @@ async function createCommand(projectName, options) {
|
|
|
7242
7285
|
const projectInfo = useDefaults ? getDefaultProjectInfo(projectName) : await promptProjectInfo(projectName);
|
|
7243
7286
|
if (!projectInfo) return;
|
|
7244
7287
|
const targetDir = resolveProjectPath(projectInfo.name);
|
|
7245
|
-
if (await
|
|
7246
|
-
const entries = await
|
|
7288
|
+
if (await fs23.pathExists(targetDir)) {
|
|
7289
|
+
const entries = await fs23.readdir(targetDir);
|
|
7247
7290
|
if (entries.length > 0) {
|
|
7248
7291
|
log.error(`Directory "${projectInfo.name}" already exists and is not empty.`);
|
|
7249
7292
|
return;
|
|
@@ -7284,7 +7327,7 @@ async function createCommand(projectName, options) {
|
|
|
7284
7327
|
log.info("Scaffolding cancelled.");
|
|
7285
7328
|
process.exit(0);
|
|
7286
7329
|
}
|
|
7287
|
-
if (!
|
|
7330
|
+
if (!fs23.pathExistsSync(RC_PATH2)) {
|
|
7288
7331
|
const { telemetryOptIn } = await prompts5(
|
|
7289
7332
|
{
|
|
7290
7333
|
type: "confirm",
|
|
@@ -7323,10 +7366,10 @@ async function createCommand(projectName, options) {
|
|
|
7323
7366
|
} catch (err) {
|
|
7324
7367
|
spinner.fail("Failed to scaffold project");
|
|
7325
7368
|
log.error(err instanceof Error ? err.message : String(err));
|
|
7326
|
-
if (await
|
|
7369
|
+
if (await fs23.pathExists(targetDir)) {
|
|
7327
7370
|
const cleanupSpinner = ora("Cleaning up...").start();
|
|
7328
7371
|
try {
|
|
7329
|
-
await
|
|
7372
|
+
await fs23.remove(targetDir);
|
|
7330
7373
|
cleanupSpinner.succeed("Cleaned up partial scaffold");
|
|
7331
7374
|
} catch {
|
|
7332
7375
|
cleanupSpinner.warn(`Could not clean up ${targetDir}. You may need to remove it manually.`);
|
|
@@ -7341,8 +7384,8 @@ function countEnabled(flags) {
|
|
|
7341
7384
|
|
|
7342
7385
|
// src/commands/doctor.ts
|
|
7343
7386
|
init_logger();
|
|
7344
|
-
import
|
|
7345
|
-
import
|
|
7387
|
+
import fs24 from "fs-extra";
|
|
7388
|
+
import path24 from "path";
|
|
7346
7389
|
import { execSync } from "child_process";
|
|
7347
7390
|
function commandExists(cmd) {
|
|
7348
7391
|
try {
|
|
@@ -7362,12 +7405,12 @@ function getVersion(cmd) {
|
|
|
7362
7405
|
async function checkForUpgrades() {
|
|
7363
7406
|
log.blank();
|
|
7364
7407
|
log.title("Upgrade Check");
|
|
7365
|
-
const packageJsonPath =
|
|
7366
|
-
if (!
|
|
7408
|
+
const packageJsonPath = path24.join(process.cwd(), "package.json");
|
|
7409
|
+
if (!fs24.pathExistsSync(packageJsonPath)) {
|
|
7367
7410
|
log.warn("No package.json found \u2014 skipping upgrade check.");
|
|
7368
7411
|
return;
|
|
7369
7412
|
}
|
|
7370
|
-
const packageJson =
|
|
7413
|
+
const packageJson = fs24.readJsonSync(packageJsonPath);
|
|
7371
7414
|
const deps = packageJson.dependencies ?? {};
|
|
7372
7415
|
const devDeps = packageJson.devDependencies ?? {};
|
|
7373
7416
|
const currentRaw = deps["@mars-stack/core"] ?? devDeps["@mars-stack/core"];
|
|
@@ -7429,25 +7472,25 @@ async function doctorCommand(options) {
|
|
|
7429
7472
|
},
|
|
7430
7473
|
{
|
|
7431
7474
|
name: "package.json exists",
|
|
7432
|
-
check: () =>
|
|
7475
|
+
check: () => fs24.pathExistsSync(path24.join(process.cwd(), "package.json"))
|
|
7433
7476
|
},
|
|
7434
7477
|
{
|
|
7435
7478
|
name: ".env file exists",
|
|
7436
|
-
check: () =>
|
|
7479
|
+
check: () => fs24.pathExistsSync(path24.join(process.cwd(), ".env"))
|
|
7437
7480
|
},
|
|
7438
7481
|
{
|
|
7439
7482
|
name: "Prisma schema exists",
|
|
7440
|
-
check: () =>
|
|
7483
|
+
check: () => fs24.pathExistsSync(path24.join(process.cwd(), "prisma", "schema")) || fs24.pathExistsSync(path24.join(process.cwd(), "prisma", "schema.prisma"))
|
|
7441
7484
|
},
|
|
7442
7485
|
{
|
|
7443
7486
|
name: "node_modules installed",
|
|
7444
|
-
check: () =>
|
|
7487
|
+
check: () => fs24.pathExistsSync(path24.join(process.cwd(), "node_modules"))
|
|
7445
7488
|
},
|
|
7446
7489
|
{
|
|
7447
7490
|
name: "DATABASE_URL set",
|
|
7448
7491
|
check: () => {
|
|
7449
7492
|
try {
|
|
7450
|
-
const envContent =
|
|
7493
|
+
const envContent = fs24.readFileSync(path24.join(process.cwd(), ".env"), "utf-8");
|
|
7451
7494
|
const match = envContent.match(/^DATABASE_URL=(.+)$/m);
|
|
7452
7495
|
return match ? match[1] !== "" : false;
|
|
7453
7496
|
} catch {
|
|
@@ -7459,7 +7502,7 @@ async function doctorCommand(options) {
|
|
|
7459
7502
|
name: "JWT_SECRET set",
|
|
7460
7503
|
check: () => {
|
|
7461
7504
|
try {
|
|
7462
|
-
const envContent =
|
|
7505
|
+
const envContent = fs24.readFileSync(path24.join(process.cwd(), ".env"), "utf-8");
|
|
7463
7506
|
const match = envContent.match(/^JWT_SECRET=(.+)$/m);
|
|
7464
7507
|
if (!match || !match[1]) return false;
|
|
7465
7508
|
return match[1].length >= 32 ? true : "set but too short (need >=32 chars)";
|
|
@@ -7504,16 +7547,16 @@ async function doctorCommand(options) {
|
|
|
7504
7547
|
|
|
7505
7548
|
// src/commands/add.ts
|
|
7506
7549
|
init_logger();
|
|
7507
|
-
import
|
|
7508
|
-
import
|
|
7550
|
+
import fs26 from "fs-extra";
|
|
7551
|
+
import path26 from "path";
|
|
7509
7552
|
import pc3 from "picocolors";
|
|
7510
7553
|
|
|
7511
7554
|
// src/utils/doc-updater.ts
|
|
7512
|
-
import
|
|
7513
|
-
import
|
|
7555
|
+
import fs25 from "fs-extra";
|
|
7556
|
+
import path25 from "path";
|
|
7514
7557
|
async function appendToDbSchema(projectDir, modelName, fields) {
|
|
7515
|
-
const filePath =
|
|
7516
|
-
if (!await
|
|
7558
|
+
const filePath = path25.join(projectDir, "docs", "generated", "db-schema.md");
|
|
7559
|
+
if (!await fs25.pathExists(filePath)) return;
|
|
7517
7560
|
const entry = [
|
|
7518
7561
|
"",
|
|
7519
7562
|
`### ${modelName}`,
|
|
@@ -7523,12 +7566,12 @@ async function appendToDbSchema(projectDir, modelName, fields) {
|
|
|
7523
7566
|
...fields.map((f) => `| ${f} | |`),
|
|
7524
7567
|
""
|
|
7525
7568
|
].join("\n");
|
|
7526
|
-
await
|
|
7569
|
+
await fs25.appendFile(filePath, entry);
|
|
7527
7570
|
}
|
|
7528
7571
|
async function updateQualityScore(projectDir, domain, grade) {
|
|
7529
|
-
const filePath =
|
|
7530
|
-
if (!await
|
|
7531
|
-
const content = await
|
|
7572
|
+
const filePath = path25.join(projectDir, "docs", "QUALITY_SCORE.md");
|
|
7573
|
+
if (!await fs25.pathExists(filePath)) return;
|
|
7574
|
+
const content = await fs25.readFile(filePath, "utf-8");
|
|
7532
7575
|
const pattern = new RegExp(`^\\|\\s*${escapeRegex(domain)}\\s*\\|`, "m");
|
|
7533
7576
|
if (pattern.test(content)) {
|
|
7534
7577
|
const updated = content.replace(pattern, (match) => {
|
|
@@ -7536,11 +7579,11 @@ async function updateQualityScore(projectDir, domain, grade) {
|
|
|
7536
7579
|
parts[2] = ` ${grade} `;
|
|
7537
7580
|
return parts.join("|");
|
|
7538
7581
|
});
|
|
7539
|
-
await
|
|
7582
|
+
await fs25.writeFile(filePath, updated);
|
|
7540
7583
|
} else {
|
|
7541
7584
|
const row = `| ${domain} | ${grade} | |
|
|
7542
7585
|
`;
|
|
7543
|
-
await
|
|
7586
|
+
await fs25.appendFile(filePath, row);
|
|
7544
7587
|
}
|
|
7545
7588
|
}
|
|
7546
7589
|
function escapeRegex(str) {
|
|
@@ -7551,8 +7594,8 @@ function escapeRegex(str) {
|
|
|
7551
7594
|
init_rollback();
|
|
7552
7595
|
function ensureInProject() {
|
|
7553
7596
|
const cwd = process.cwd();
|
|
7554
|
-
const configPath =
|
|
7555
|
-
if (!
|
|
7597
|
+
const configPath = path26.join(cwd, "src", "config", "app.config.ts");
|
|
7598
|
+
if (!fs26.pathExistsSync(configPath)) {
|
|
7556
7599
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7557
7600
|
process.exit(1);
|
|
7558
7601
|
}
|
|
@@ -7578,15 +7621,98 @@ function pluralize(word) {
|
|
|
7578
7621
|
return word + "s";
|
|
7579
7622
|
}
|
|
7580
7623
|
function stripBrackets(segment) {
|
|
7581
|
-
return segment.replace(/[\[\]]/g, "");
|
|
7624
|
+
return segment.replace(/[\[\]\.]/g, "");
|
|
7625
|
+
}
|
|
7626
|
+
function parseDynamicParams(routePath) {
|
|
7627
|
+
const segments = routePath.split("/").filter(Boolean);
|
|
7628
|
+
const params = [];
|
|
7629
|
+
for (const segment of segments) {
|
|
7630
|
+
if (/^\[/.test(segment)) {
|
|
7631
|
+
const catchAll = /^\[\.\.\./.test(segment);
|
|
7632
|
+
const name = segment.replace(/^\[(?:\.\.\.)?/, "").replace(/\]$/, "");
|
|
7633
|
+
params.push({ name, catchAll });
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
return params;
|
|
7637
|
+
}
|
|
7638
|
+
function buildPageContent(pageName, dynamicParams) {
|
|
7639
|
+
const hasParams = dynamicParams.length > 0;
|
|
7640
|
+
if (!hasParams) {
|
|
7641
|
+
return `import { Card, CardHeader, CardBody } from '@mars-stack/ui';
|
|
7642
|
+
import type { Metadata } from 'next';
|
|
7643
|
+
|
|
7644
|
+
export const metadata: Metadata = {
|
|
7645
|
+
title: '${pageName}',
|
|
7646
|
+
};
|
|
7647
|
+
|
|
7648
|
+
export default function ${pageName}Page() {
|
|
7649
|
+
return (
|
|
7650
|
+
<div className="mx-auto max-w-4xl space-y-6 p-6">
|
|
7651
|
+
<div>
|
|
7652
|
+
<h1 className="text-4xl font-bold text-text-primary mb-4">${pageName}</h1>
|
|
7653
|
+
<p className="text-base text-text-secondary mb-4">
|
|
7654
|
+
Description goes here.
|
|
7655
|
+
</p>
|
|
7656
|
+
</div>
|
|
7657
|
+
|
|
7658
|
+
<Card>
|
|
7659
|
+
<CardHeader>
|
|
7660
|
+
<h2 className="text-lg font-semibold text-text-primary">${pageName}</h2>
|
|
7661
|
+
</CardHeader>
|
|
7662
|
+
<CardBody>
|
|
7663
|
+
{/* Content */}
|
|
7664
|
+
</CardBody>
|
|
7665
|
+
</Card>
|
|
7666
|
+
</div>
|
|
7667
|
+
);
|
|
7668
|
+
}
|
|
7669
|
+
`;
|
|
7670
|
+
}
|
|
7671
|
+
const paramFields = dynamicParams.map((p) => `${p.name}: ${p.catchAll ? "string[]" : "string"}`).join("; ");
|
|
7672
|
+
const destructuredNames = dynamicParams.map((p) => p.name).join(", ");
|
|
7673
|
+
return `import { Card, CardHeader, CardBody } from '@mars-stack/ui';
|
|
7674
|
+
import type { Metadata } from 'next';
|
|
7675
|
+
|
|
7676
|
+
export const metadata: Metadata = {
|
|
7677
|
+
title: '${pageName}',
|
|
7678
|
+
};
|
|
7679
|
+
|
|
7680
|
+
interface Props {
|
|
7681
|
+
params: Promise<{ ${paramFields} }>;
|
|
7682
|
+
}
|
|
7683
|
+
|
|
7684
|
+
export default async function ${pageName}Page({ params }: Props) {
|
|
7685
|
+
const { ${destructuredNames} } = await params;
|
|
7686
|
+
|
|
7687
|
+
return (
|
|
7688
|
+
<div className="mx-auto max-w-4xl space-y-6 p-6">
|
|
7689
|
+
<div>
|
|
7690
|
+
<h1 className="text-4xl font-bold text-text-primary mb-4">{${dynamicParams[0].name}}</h1>
|
|
7691
|
+
<p className="text-base text-text-secondary mb-4">
|
|
7692
|
+
Description goes here.
|
|
7693
|
+
</p>
|
|
7694
|
+
</div>
|
|
7695
|
+
|
|
7696
|
+
<Card>
|
|
7697
|
+
<CardHeader>
|
|
7698
|
+
<h2 className="text-lg font-semibold text-text-primary">${pageName}</h2>
|
|
7699
|
+
</CardHeader>
|
|
7700
|
+
<CardBody>
|
|
7701
|
+
{/* Content */}
|
|
7702
|
+
</CardBody>
|
|
7703
|
+
</Card>
|
|
7704
|
+
</div>
|
|
7705
|
+
);
|
|
7706
|
+
}
|
|
7707
|
+
`;
|
|
7582
7708
|
}
|
|
7583
7709
|
async function addFeatureCommand(name) {
|
|
7584
7710
|
const root = ensureInProject();
|
|
7585
7711
|
const kebab = toKebab(name);
|
|
7586
7712
|
const pascal = toPascal(name);
|
|
7587
7713
|
const camel = toCamel(name);
|
|
7588
|
-
const featureDir =
|
|
7589
|
-
if (await
|
|
7714
|
+
const featureDir = path26.join(root, "src", "features", kebab);
|
|
7715
|
+
if (await fs26.pathExists(featureDir)) {
|
|
7590
7716
|
log.error(`Feature "${kebab}" already exists at src/features/${kebab}/`);
|
|
7591
7717
|
return;
|
|
7592
7718
|
}
|
|
@@ -7651,9 +7777,9 @@ export type Update${pascal}Input = z.infer<typeof ${camel}Schemas.update>;
|
|
|
7651
7777
|
ctx.trackCreatedFile(featureDir);
|
|
7652
7778
|
let count = 0;
|
|
7653
7779
|
for (const [filePath, content] of Object.entries(files)) {
|
|
7654
|
-
const fullPath =
|
|
7655
|
-
await
|
|
7656
|
-
await
|
|
7780
|
+
const fullPath = path26.join(featureDir, filePath);
|
|
7781
|
+
await fs26.ensureDir(path26.dirname(fullPath));
|
|
7782
|
+
await fs26.writeFile(fullPath, content);
|
|
7657
7783
|
count++;
|
|
7658
7784
|
}
|
|
7659
7785
|
await ctx.commit();
|
|
@@ -7674,42 +7800,15 @@ async function addPageCommand(routePath, options) {
|
|
|
7674
7800
|
const root = ensureInProject();
|
|
7675
7801
|
const cleanPath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
|
|
7676
7802
|
const group = options.protected ? "(protected)" : "(public)";
|
|
7677
|
-
const pageDir =
|
|
7678
|
-
if (await
|
|
7803
|
+
const pageDir = path26.join(root, "src", "app", group, cleanPath);
|
|
7804
|
+
if (await fs26.pathExists(path26.join(pageDir, "page.tsx"))) {
|
|
7679
7805
|
log.error(`Page already exists at src/app/${group}/${cleanPath}/page.tsx`);
|
|
7680
7806
|
return;
|
|
7681
7807
|
}
|
|
7682
7808
|
const lastSegment = cleanPath.split("/").pop() || "page";
|
|
7683
7809
|
const pageName = toPascal(stripBrackets(lastSegment));
|
|
7684
|
-
const
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
export const metadata: Metadata = {
|
|
7688
|
-
title: '${pageName}',
|
|
7689
|
-
};
|
|
7690
|
-
|
|
7691
|
-
export default function ${pageName}Page() {
|
|
7692
|
-
return (
|
|
7693
|
-
<div className="mx-auto max-w-4xl space-y-6 p-6">
|
|
7694
|
-
<div>
|
|
7695
|
-
<h1 className="text-4xl font-bold text-text-primary mb-4">${pageName}</h1>
|
|
7696
|
-
<p className="text-base text-text-secondary mb-4">
|
|
7697
|
-
Description goes here.
|
|
7698
|
-
</p>
|
|
7699
|
-
</div>
|
|
7700
|
-
|
|
7701
|
-
<Card>
|
|
7702
|
-
<CardHeader>
|
|
7703
|
-
<h2 className="text-lg font-semibold text-text-primary">${pageName}</h2>
|
|
7704
|
-
</CardHeader>
|
|
7705
|
-
<CardBody>
|
|
7706
|
-
{/* Content */}
|
|
7707
|
-
</CardBody>
|
|
7708
|
-
</Card>
|
|
7709
|
-
</div>
|
|
7710
|
-
);
|
|
7711
|
-
}
|
|
7712
|
-
`;
|
|
7810
|
+
const dynamicParams = parseDynamicParams(cleanPath);
|
|
7811
|
+
const pageContent = buildPageContent(pageName, dynamicParams);
|
|
7713
7812
|
const loadingContent = `import { Spinner } from '@mars-stack/ui';
|
|
7714
7813
|
|
|
7715
7814
|
export default function Loading() {
|
|
@@ -7723,9 +7822,9 @@ export default function Loading() {
|
|
|
7723
7822
|
const ctx = createRollbackContext();
|
|
7724
7823
|
try {
|
|
7725
7824
|
ctx.trackCreatedFile(pageDir);
|
|
7726
|
-
await
|
|
7727
|
-
await
|
|
7728
|
-
await
|
|
7825
|
+
await fs26.ensureDir(pageDir);
|
|
7826
|
+
await fs26.writeFile(path26.join(pageDir, "page.tsx"), pageContent);
|
|
7827
|
+
await fs26.writeFile(path26.join(pageDir, "loading.tsx"), loadingContent);
|
|
7729
7828
|
await ctx.commit();
|
|
7730
7829
|
log.success(`Created page at ${pc3.bold(`src/app/${group}/${cleanPath}/`)}`);
|
|
7731
7830
|
trackEvent("add", { type: "page" });
|
|
@@ -7742,9 +7841,9 @@ async function addModelCommand(name) {
|
|
|
7742
7841
|
const pascal = toPascal(name);
|
|
7743
7842
|
const camel = toCamel(name);
|
|
7744
7843
|
const kebab = toKebab(name);
|
|
7745
|
-
const schemaDir =
|
|
7746
|
-
const schemaFile =
|
|
7747
|
-
if (await
|
|
7844
|
+
const schemaDir = path26.join(root, "prisma", "schema");
|
|
7845
|
+
const schemaFile = path26.join(schemaDir, `${kebab}.prisma`);
|
|
7846
|
+
if (await fs26.pathExists(schemaFile)) {
|
|
7748
7847
|
log.error(`Schema file already exists: prisma/schema/${kebab}.prisma`);
|
|
7749
7848
|
return;
|
|
7750
7849
|
}
|
|
@@ -7762,8 +7861,8 @@ async function addModelCommand(name) {
|
|
|
7762
7861
|
const ctx = createRollbackContext();
|
|
7763
7862
|
try {
|
|
7764
7863
|
ctx.trackCreatedFile(schemaFile);
|
|
7765
|
-
await
|
|
7766
|
-
await
|
|
7864
|
+
await fs26.ensureDir(schemaDir);
|
|
7865
|
+
await fs26.writeFile(schemaFile, content);
|
|
7767
7866
|
await ctx.commit();
|
|
7768
7867
|
log.success(`Created model ${pc3.bold(pascal)} at prisma/schema/${kebab}.prisma`);
|
|
7769
7868
|
trackEvent("add", { type: "model" });
|
|
@@ -7785,9 +7884,9 @@ async function addEmailCommand(name) {
|
|
|
7785
7884
|
const kebab = toKebab(name);
|
|
7786
7885
|
const pascal = toPascal(name);
|
|
7787
7886
|
const camel = toCamel(name);
|
|
7788
|
-
const templatesDir =
|
|
7789
|
-
const filePath =
|
|
7790
|
-
if (await
|
|
7887
|
+
const templatesDir = path26.join(root, "src", "lib", "core", "email", "templates");
|
|
7888
|
+
const filePath = path26.join(templatesDir, `${kebab}-email.ts`);
|
|
7889
|
+
if (await fs26.pathExists(filePath)) {
|
|
7791
7890
|
log.error(`Email template already exists: src/lib/core/email/templates/${kebab}-email.ts`);
|
|
7792
7891
|
return;
|
|
7793
7892
|
}
|
|
@@ -7831,25 +7930,25 @@ export function ${functionName}({ appName, actionUrl, userName }: ${pascal}Email
|
|
|
7831
7930
|
}
|
|
7832
7931
|
`;
|
|
7833
7932
|
const ctx = createRollbackContext();
|
|
7834
|
-
const indexPath =
|
|
7933
|
+
const indexPath = path26.join(templatesDir, "index.ts");
|
|
7835
7934
|
try {
|
|
7836
7935
|
ctx.trackCreatedFile(filePath);
|
|
7837
|
-
if (await
|
|
7936
|
+
if (await fs26.pathExists(indexPath)) {
|
|
7838
7937
|
await ctx.trackModifiedFile(indexPath);
|
|
7839
7938
|
} else {
|
|
7840
7939
|
ctx.trackCreatedFile(indexPath);
|
|
7841
7940
|
}
|
|
7842
|
-
await
|
|
7843
|
-
await
|
|
7941
|
+
await fs26.ensureDir(templatesDir);
|
|
7942
|
+
await fs26.writeFile(filePath, content);
|
|
7844
7943
|
const exportLine = `export { ${functionName} } from './${kebab}-email';
|
|
7845
7944
|
`;
|
|
7846
|
-
if (await
|
|
7847
|
-
const existing = await
|
|
7945
|
+
if (await fs26.pathExists(indexPath)) {
|
|
7946
|
+
const existing = await fs26.readFile(indexPath, "utf-8");
|
|
7848
7947
|
if (!existing.includes(functionName)) {
|
|
7849
|
-
await
|
|
7948
|
+
await fs26.appendFile(indexPath, exportLine);
|
|
7850
7949
|
}
|
|
7851
7950
|
} else {
|
|
7852
|
-
await
|
|
7951
|
+
await fs26.writeFile(indexPath, exportLine);
|
|
7853
7952
|
}
|
|
7854
7953
|
await ctx.commit();
|
|
7855
7954
|
log.success(`Created email template ${pc3.bold(kebab)} at src/lib/core/email/templates/${kebab}-email.ts`);
|
|
@@ -7872,10 +7971,10 @@ async function addComponentCommand(name, options) {
|
|
|
7872
7971
|
log.error(`Invalid type "${type}". Use: ${validTypes.join(", ")}`);
|
|
7873
7972
|
return;
|
|
7874
7973
|
}
|
|
7875
|
-
const dir = type === "primitive" ?
|
|
7876
|
-
await
|
|
7877
|
-
const filePath =
|
|
7878
|
-
if (await
|
|
7974
|
+
const dir = type === "primitive" ? path26.join(root, "src", "components", "primitives") : path26.join(root, "src", "components", "patterns");
|
|
7975
|
+
await fs26.ensureDir(dir);
|
|
7976
|
+
const filePath = path26.join(dir, `${pascal}.tsx`);
|
|
7977
|
+
if (await fs26.pathExists(filePath)) {
|
|
7879
7978
|
log.error(`Component already exists: ${pascal}.tsx`);
|
|
7880
7979
|
return;
|
|
7881
7980
|
}
|
|
@@ -7924,9 +8023,9 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7924
8023
|
const ctx = createRollbackContext();
|
|
7925
8024
|
try {
|
|
7926
8025
|
ctx.trackCreatedFile(filePath);
|
|
7927
|
-
await
|
|
8026
|
+
await fs26.writeFile(filePath, content);
|
|
7928
8027
|
await ctx.commit();
|
|
7929
|
-
log.success(`Created ${type} component ${pc3.bold(pascal)} at ${
|
|
8028
|
+
log.success(`Created ${type} component ${pc3.bold(pascal)} at ${path26.relative(root, filePath)}`);
|
|
7930
8029
|
trackEvent("add", { type: "component", componentType: type });
|
|
7931
8030
|
log.blank();
|
|
7932
8031
|
log.warn(`Remember to add the export to the barrel file:`);
|
|
@@ -7941,14 +8040,14 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7941
8040
|
// src/commands/configure.ts
|
|
7942
8041
|
init_logger();
|
|
7943
8042
|
import { execSync as execSync2 } from "child_process";
|
|
7944
|
-
import
|
|
7945
|
-
import
|
|
8043
|
+
import fs27 from "fs-extra";
|
|
8044
|
+
import path27 from "path";
|
|
7946
8045
|
import pc4 from "picocolors";
|
|
7947
8046
|
import prompts6 from "prompts";
|
|
7948
8047
|
function ensureInProject2() {
|
|
7949
8048
|
const cwd = process.cwd();
|
|
7950
|
-
const configPath =
|
|
7951
|
-
if (!
|
|
8049
|
+
const configPath = path27.join(cwd, "src", "config", "app.config.ts");
|
|
8050
|
+
if (!fs27.pathExistsSync(configPath)) {
|
|
7952
8051
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7953
8052
|
process.exit(1);
|
|
7954
8053
|
}
|
|
@@ -7962,8 +8061,8 @@ var PROVIDER_DEPENDENCIES = {
|
|
|
7962
8061
|
"storage:s3": ["@aws-sdk/client-s3", "@aws-sdk/s3-request-presigner"]
|
|
7963
8062
|
};
|
|
7964
8063
|
function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
7965
|
-
const configPath =
|
|
7966
|
-
let content =
|
|
8064
|
+
const configPath = path27.join(projectDir, "src", "config", "app.config.ts");
|
|
8065
|
+
let content = fs27.readFileSync(configPath, "utf-8");
|
|
7967
8066
|
if (serviceKey === "auth") {
|
|
7968
8067
|
const boolValue = provider === "google" ? "true" : "false";
|
|
7969
8068
|
const featureRegex = new RegExp(`(googleOAuth\\s*:\\s*)(?:true|false)`);
|
|
@@ -7978,10 +8077,10 @@ function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
|
7978
8077
|
content = content.replace(featureRegex, `$1true`);
|
|
7979
8078
|
}
|
|
7980
8079
|
}
|
|
7981
|
-
|
|
8080
|
+
fs27.writeFileSync(configPath, content);
|
|
7982
8081
|
}
|
|
7983
8082
|
function detectPackageManager(projectDir) {
|
|
7984
|
-
if (
|
|
8083
|
+
if (fs27.existsSync(path27.join(projectDir, "yarn.lock"))) return "yarn";
|
|
7985
8084
|
return "npm";
|
|
7986
8085
|
}
|
|
7987
8086
|
function installDependencies(projectDir, deps) {
|
|
@@ -8091,15 +8190,15 @@ async function configureCommand(service) {
|
|
|
8091
8190
|
log.step(`Manually set ${pc4.bold(`services.${serviceConfig.configKey}.provider`)} to ${pc4.bold(`'${provider}'`)} in ${pc4.dim("src/config/app.config.ts")}`);
|
|
8092
8191
|
}
|
|
8093
8192
|
if (selectedProvider.envVars.length > 0) {
|
|
8094
|
-
const envPath =
|
|
8095
|
-
if (await
|
|
8096
|
-
const envContent = await
|
|
8193
|
+
const envPath = path27.join(root, ".env");
|
|
8194
|
+
if (await fs27.pathExists(envPath)) {
|
|
8195
|
+
const envContent = await fs27.readFile(envPath, "utf-8");
|
|
8097
8196
|
const missingVars = selectedProvider.envVars.filter(
|
|
8098
8197
|
(v) => !envContent.includes(v)
|
|
8099
8198
|
);
|
|
8100
8199
|
if (missingVars.length > 0) {
|
|
8101
8200
|
const additions = missingVars.map((v) => `${v}=""`).join("\n");
|
|
8102
|
-
await
|
|
8201
|
+
await fs27.appendFile(envPath, `
|
|
8103
8202
|
# ${selectedService} (${provider})
|
|
8104
8203
|
${additions}
|
|
8105
8204
|
`);
|
|
@@ -8128,15 +8227,15 @@ ${additions}
|
|
|
8128
8227
|
|
|
8129
8228
|
// src/commands/deploy.ts
|
|
8130
8229
|
init_logger();
|
|
8131
|
-
import
|
|
8132
|
-
import
|
|
8230
|
+
import fs28 from "fs-extra";
|
|
8231
|
+
import path28 from "path";
|
|
8133
8232
|
import { execSync as execSync3 } from "child_process";
|
|
8134
8233
|
import pc5 from "picocolors";
|
|
8135
8234
|
import prompts7 from "prompts";
|
|
8136
8235
|
function ensureInProject3() {
|
|
8137
8236
|
const cwd = process.cwd();
|
|
8138
|
-
const configPath =
|
|
8139
|
-
if (!
|
|
8237
|
+
const configPath = path28.join(cwd, "src", "config", "app.config.ts");
|
|
8238
|
+
if (!fs28.pathExistsSync(configPath)) {
|
|
8140
8239
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
8141
8240
|
process.exit(1);
|
|
8142
8241
|
}
|
|
@@ -8175,8 +8274,8 @@ async function deployCommand() {
|
|
|
8175
8274
|
return;
|
|
8176
8275
|
}
|
|
8177
8276
|
}
|
|
8178
|
-
const vercelDir =
|
|
8179
|
-
if (!
|
|
8277
|
+
const vercelDir = path28.join(root, ".vercel");
|
|
8278
|
+
if (!fs28.existsSync(vercelDir)) {
|
|
8180
8279
|
log.step("Linking project to Vercel...");
|
|
8181
8280
|
try {
|
|
8182
8281
|
execSync3("vercel link", { cwd: root, stdio: "inherit" });
|
|
@@ -8193,8 +8292,8 @@ async function deployCommand() {
|
|
|
8193
8292
|
log.blank();
|
|
8194
8293
|
const requiredVars = ["JWT_SECRET", "DATABASE_URL"];
|
|
8195
8294
|
log.step(`Core: ${pc5.dim(requiredVars.join(", "))}`);
|
|
8196
|
-
const configPath =
|
|
8197
|
-
const configContent = await
|
|
8295
|
+
const configPath = path28.join(root, "src", "config", "app.config.ts");
|
|
8296
|
+
const configContent = await fs28.readFile(configPath, "utf-8");
|
|
8198
8297
|
if (configContent.includes("email: { provider: 'sendgrid'")) {
|
|
8199
8298
|
log.step(`Email (SendGrid): ${pc5.dim("SENDGRID_API_KEY, SENDGRID_FROM_EMAIL")}`);
|
|
8200
8299
|
} else if (configContent.includes("email: { provider: 'resend'")) {
|
|
@@ -8255,8 +8354,8 @@ async function deployCommand() {
|
|
|
8255
8354
|
|
|
8256
8355
|
// src/commands/upgrade.ts
|
|
8257
8356
|
init_logger();
|
|
8258
|
-
import
|
|
8259
|
-
import
|
|
8357
|
+
import fs29 from "fs-extra";
|
|
8358
|
+
import path29 from "path";
|
|
8260
8359
|
import { execSync as execSync4 } from "child_process";
|
|
8261
8360
|
var MARS_PACKAGES = ["@mars-stack/core", "@mars-stack/ui"];
|
|
8262
8361
|
var CHANGELOG_URL = "https://github.com/greaveselliott/mars/blob/main/CHANGELOG.md";
|
|
@@ -8279,7 +8378,7 @@ function readCurrentVersion(packageJson, packageName) {
|
|
|
8279
8378
|
return version ? version.replace(/^[\^~]/, "") : null;
|
|
8280
8379
|
}
|
|
8281
8380
|
function detectPackageManager2(projectDir) {
|
|
8282
|
-
if (
|
|
8381
|
+
if (fs29.pathExistsSync(path29.join(projectDir, "yarn.lock"))) {
|
|
8283
8382
|
return "yarn";
|
|
8284
8383
|
}
|
|
8285
8384
|
return "npm";
|
|
@@ -8343,101 +8442,104 @@ function updatePackageJsonVersions(packageJsonPath, packageJson, versions) {
|
|
|
8343
8442
|
if (updated.length > 0) {
|
|
8344
8443
|
packageJson.dependencies = deps;
|
|
8345
8444
|
packageJson.devDependencies = devDeps;
|
|
8346
|
-
|
|
8445
|
+
fs29.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
8347
8446
|
}
|
|
8348
8447
|
return updated;
|
|
8349
8448
|
}
|
|
8350
|
-
function
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
if (!fs28.pathExistsSync(packageJsonPath)) {
|
|
8358
|
-
log.error(
|
|
8359
|
-
"No package.json found. Are you in a Mars project directory?"
|
|
8360
|
-
);
|
|
8361
|
-
process.exitCode = 1;
|
|
8362
|
-
return;
|
|
8363
|
-
}
|
|
8364
|
-
const packageJson = fs28.readJsonSync(packageJsonPath);
|
|
8365
|
-
const hasMarsPackage = MARS_PACKAGES.some(
|
|
8366
|
-
(name) => readCurrentVersion(packageJson, name) !== null
|
|
8449
|
+
async function runUpgrade(options) {
|
|
8450
|
+
log.title("MARS Upgrade");
|
|
8451
|
+
const projectDir = process.cwd();
|
|
8452
|
+
const packageJsonPath = path29.join(projectDir, "package.json");
|
|
8453
|
+
if (!fs29.pathExistsSync(packageJsonPath)) {
|
|
8454
|
+
log.error(
|
|
8455
|
+
"No package.json found. Are you in a Mars project directory?"
|
|
8367
8456
|
);
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
(v) => v.current !== null && v.latest === null
|
|
8457
|
+
process.exitCode = 1;
|
|
8458
|
+
return;
|
|
8459
|
+
}
|
|
8460
|
+
const packageJson = fs29.readJsonSync(packageJsonPath);
|
|
8461
|
+
const hasMarsPackage = MARS_PACKAGES.some(
|
|
8462
|
+
(name) => readCurrentVersion(packageJson, name) !== null
|
|
8463
|
+
);
|
|
8464
|
+
if (!hasMarsPackage) {
|
|
8465
|
+
log.error(
|
|
8466
|
+
"No @mars-stack packages found in dependencies. This does not appear to be a Mars project."
|
|
8379
8467
|
);
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
if (options.dryRun) {
|
|
8392
|
-
log.info("Dry run \u2014 no changes were made.");
|
|
8393
|
-
log.info(`Run ${`mars upgrade`} without --dry-run to apply updates.`);
|
|
8394
|
-
return;
|
|
8395
|
-
}
|
|
8396
|
-
log.step("Updating package.json...");
|
|
8397
|
-
const updated = updatePackageJsonVersions(
|
|
8398
|
-
packageJsonPath,
|
|
8399
|
-
packageJson,
|
|
8400
|
-
versions
|
|
8468
|
+
process.exitCode = 1;
|
|
8469
|
+
return;
|
|
8470
|
+
}
|
|
8471
|
+
log.step("Checking for updates...");
|
|
8472
|
+
const versions = await gatherVersionInfo(packageJson);
|
|
8473
|
+
const registryErrors = versions.filter(
|
|
8474
|
+
(v) => v.current !== null && v.latest === null
|
|
8475
|
+
);
|
|
8476
|
+
if (registryErrors.length > 0) {
|
|
8477
|
+
log.warn(
|
|
8478
|
+
"Could not reach the npm registry for some packages. Check your network connection."
|
|
8401
8479
|
);
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8480
|
+
}
|
|
8481
|
+
printVersionTable(versions);
|
|
8482
|
+
const updatesAvailable = versions.some((v) => v.updateAvailable);
|
|
8483
|
+
if (!updatesAvailable) {
|
|
8484
|
+
log.success("All Mars packages are up to date.");
|
|
8485
|
+
return;
|
|
8486
|
+
}
|
|
8487
|
+
if (options?.dryRun) {
|
|
8488
|
+
log.info("Dry run \u2014 no changes were made.");
|
|
8489
|
+
log.info(`Run ${`mars upgrade`} without --dry-run to apply updates.`);
|
|
8490
|
+
return;
|
|
8491
|
+
}
|
|
8492
|
+
log.step("Updating package.json...");
|
|
8493
|
+
const updated = updatePackageJsonVersions(
|
|
8494
|
+
packageJsonPath,
|
|
8495
|
+
packageJson,
|
|
8496
|
+
versions
|
|
8497
|
+
);
|
|
8498
|
+
for (const change of updated) {
|
|
8499
|
+
log.success(change);
|
|
8500
|
+
}
|
|
8501
|
+
const pm = detectPackageManager2(projectDir);
|
|
8502
|
+
const installCmd = getInstallCommand(pm);
|
|
8503
|
+
log.blank();
|
|
8504
|
+
log.step(`Running ${installCmd}...`);
|
|
8505
|
+
try {
|
|
8506
|
+
execSync4(installCmd, {
|
|
8507
|
+
cwd: projectDir,
|
|
8508
|
+
stdio: "inherit"
|
|
8509
|
+
});
|
|
8510
|
+
} catch {
|
|
8511
|
+
log.error(`${installCmd} failed. You may need to run it manually.`);
|
|
8512
|
+
process.exitCode = 1;
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8515
|
+
log.blank();
|
|
8516
|
+
log.step("Running mars doctor to verify...");
|
|
8517
|
+
try {
|
|
8518
|
+
execSync4("npx mars doctor", {
|
|
8519
|
+
cwd: projectDir,
|
|
8520
|
+
stdio: "inherit"
|
|
8521
|
+
});
|
|
8522
|
+
} catch {
|
|
8523
|
+
log.warn(
|
|
8524
|
+
"mars doctor reported issues. Review the output above."
|
|
8440
8525
|
);
|
|
8526
|
+
}
|
|
8527
|
+
log.blank();
|
|
8528
|
+
log.title("Upgrade Summary");
|
|
8529
|
+
for (const change of updated) {
|
|
8530
|
+
log.success(change);
|
|
8531
|
+
}
|
|
8532
|
+
log.blank();
|
|
8533
|
+
log.info(`Changelog: ${CHANGELOG_URL}`);
|
|
8534
|
+
log.info(
|
|
8535
|
+
"Review the changelog for any breaking changes or migration notes."
|
|
8536
|
+
);
|
|
8537
|
+
}
|
|
8538
|
+
function upgradeCommand(program2) {
|
|
8539
|
+
program2.command("upgrade").description(
|
|
8540
|
+
"Upgrade @mars-stack/core, @mars-stack/ui, and @mars-stack/cli to the latest versions"
|
|
8541
|
+
).option("--dry-run", "Show what would be updated without making changes").action(async (options) => {
|
|
8542
|
+
await runUpgrade(options);
|
|
8441
8543
|
});
|
|
8442
8544
|
}
|
|
8443
8545
|
|
|
@@ -8457,10 +8559,7 @@ init_sentry();
|
|
|
8457
8559
|
init_feature_flags();
|
|
8458
8560
|
init_logger();
|
|
8459
8561
|
var program = new Command();
|
|
8460
|
-
program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(getCliVersion()).option("-
|
|
8461
|
-
function isVerbose() {
|
|
8462
|
-
return program.opts().verbose === true;
|
|
8463
|
-
}
|
|
8562
|
+
program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(getCliVersion(), "-v, --version", "Print the current mars CLI version").option("-u, --upgrade", "Upgrade Mars packages to latest versions");
|
|
8464
8563
|
program.command("create").description("Create a new MARS project").argument("[name]", "Project name (kebab-case)").option("--defaults", "Skip prompts, use default configuration").action(async (name, options) => {
|
|
8465
8564
|
await createCommand(name, options);
|
|
8466
8565
|
});
|
|
@@ -8541,8 +8640,17 @@ program.command("telemetry").description("Manage anonymous usage telemetry").arg
|
|
|
8541
8640
|
log.error("Usage: mars telemetry <enable|disable>");
|
|
8542
8641
|
}
|
|
8543
8642
|
});
|
|
8544
|
-
program.
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8643
|
+
program.hook("preAction", (thisCommand) => {
|
|
8644
|
+
const globalOpts = program.opts();
|
|
8645
|
+
if (globalOpts.upgrade && thisCommand !== program) {
|
|
8646
|
+
log.error('Cannot combine --upgrade with other commands. Use "mars -u" or "mars upgrade" alone.');
|
|
8647
|
+
process.exit(1);
|
|
8648
|
+
}
|
|
8649
|
+
});
|
|
8650
|
+
await program.parseAsync();
|
|
8651
|
+
var opts = program.opts();
|
|
8652
|
+
if (opts.upgrade && program.args.length === 0) {
|
|
8653
|
+
await runUpgrade();
|
|
8654
|
+
process.exit(0);
|
|
8655
|
+
}
|
|
8548
8656
|
//# sourceMappingURL=index.js.map
|