@mars-stack/cli 2.0.1 → 3.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 +252 -241
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template/.cursor/skills/mars-address-pr-comments/SKILL.md +129 -0
- package/template/AGENTS.md +3 -2
- /package/template/.cursor/rules/{composition-patterns.mdc → mars-composition-patterns.mdc} +0 -0
- /package/template/.cursor/rules/{data-access.mdc → mars-data-access.mdc} +0 -0
- /package/template/.cursor/rules/{project-structure.mdc → mars-project-structure.mdc} +0 -0
- /package/template/.cursor/rules/{security.mdc → mars-security.mdc} +0 -0
- /package/template/.cursor/rules/{testing.mdc → mars-testing.mdc} +0 -0
- /package/template/.cursor/rules/{ui-conventions.mdc → mars-ui-conventions.mdc} +0 -0
- /package/template/.cursor/skills/{add-api-route → mars-add-api-route}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-audit-log → mars-add-audit-log}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-blog → mars-add-blog}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-command-palette → mars-add-command-palette}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-component → mars-add-component}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-crud-routes → mars-add-crud-routes}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-e2e-test → mars-add-e2e-test}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-error-boundary → mars-add-error-boundary}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-feature → mars-add-feature}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-middleware → mars-add-middleware}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-page → mars-add-page}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-prisma-model → mars-add-prisma-model}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-protected-resource → mars-add-protected-resource}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-role → mars-add-role}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-server-action → mars-add-server-action}/SKILL.md +0 -0
- /package/template/.cursor/skills/{add-webhook → mars-add-webhook}/SKILL.md +0 -0
- /package/template/.cursor/skills/{build-complete-feature → mars-build-complete-feature}/SKILL.md +0 -0
- /package/template/.cursor/skills/{build-dashboard → mars-build-dashboard}/SKILL.md +0 -0
- /package/template/.cursor/skills/{build-data-table → mars-build-data-table}/SKILL.md +0 -0
- /package/template/.cursor/skills/{build-form → mars-build-form}/SKILL.md +0 -0
- /package/template/.cursor/skills/{build-landing-page → mars-build-landing-page}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-ai → mars-configure-ai}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-analytics → mars-configure-analytics}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-dark-mode → mars-configure-dark-mode}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-email → mars-configure-email}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-email-verification → mars-configure-email-verification}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-feature-flags → mars-configure-feature-flags}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-i18n → mars-configure-i18n}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-jobs → mars-configure-jobs}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-magic-links → mars-configure-magic-links}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-multi-tenancy → mars-configure-multi-tenancy}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-notifications → mars-configure-notifications}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-oauth → mars-configure-oauth}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-onboarding → mars-configure-onboarding}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-payments → mars-configure-payments}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-realtime → mars-configure-realtime}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-search → mars-configure-search}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-storage → mars-configure-storage}/SKILL.md +0 -0
- /package/template/.cursor/skills/{configure-two-factor → mars-configure-two-factor}/SKILL.md +0 -0
- /package/template/.cursor/skills/{create-execution-plan → mars-create-execution-plan}/SKILL.md +0 -0
- /package/template/.cursor/skills/{create-seed → mars-create-seed}/SKILL.md +0 -0
- /package/template/.cursor/skills/{deploy-to-vercel → mars-deploy-to-vercel}/SKILL.md +0 -0
- /package/template/.cursor/skills/{design-tokens → mars-design-tokens}/SKILL.md +0 -0
- /package/template/.cursor/skills/{setup-billing → mars-setup-billing}/SKILL.md +0 -0
- /package/template/.cursor/skills/{setup-project → mars-setup-project}/SKILL.md +0 -0
- /package/template/.cursor/skills/{setup-teams → mars-setup-teams}/SKILL.md +0 -0
- /package/template/.cursor/skills/{test-api-route → mars-test-api-route}/SKILL.md +0 -0
- /package/template/.cursor/skills/{update-architecture-docs → mars-update-architecture-docs}/SKILL.md +0 -0
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ var init_logger = __esm({
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
// src/utils/rollback.ts
|
|
47
|
-
import
|
|
47
|
+
import fs4 from "fs-extra";
|
|
48
48
|
import path4 from "path";
|
|
49
49
|
import os from "os";
|
|
50
50
|
function createRollbackContext() {
|
|
@@ -58,10 +58,10 @@ function createRollbackContext() {
|
|
|
58
58
|
manifest.filesCreated.push(filePath);
|
|
59
59
|
}
|
|
60
60
|
async function trackModifiedFile(filePath) {
|
|
61
|
-
if (!await
|
|
62
|
-
await
|
|
61
|
+
if (!await fs4.pathExists(filePath)) return;
|
|
62
|
+
await fs4.ensureDir(backupDir);
|
|
63
63
|
const backupPath = path4.join(backupDir, `${manifest.filesModified.length}-${path4.basename(filePath)}`);
|
|
64
|
-
await
|
|
64
|
+
await fs4.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 fs4.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 fs4.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 fs4.pathExists(backupDir)) {
|
|
90
90
|
try {
|
|
91
|
-
await
|
|
91
|
+
await fs4.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
|
|
111
|
+
import fs5 from "fs-extra";
|
|
112
112
|
import path5 from "path";
|
|
113
113
|
async function addDependencies(projectRoot, deps, dev = false) {
|
|
114
114
|
const pkgPath = path5.join(projectRoot, "package.json");
|
|
115
|
-
if (!await
|
|
116
|
-
const pkg = await
|
|
115
|
+
if (!await fs5.pathExists(pkgPath)) return;
|
|
116
|
+
const pkg = await fs5.readJson(pkgPath);
|
|
117
117
|
const key = dev ? "devDependencies" : "dependencies";
|
|
118
118
|
pkg[key] = { ...pkg[key], ...deps };
|
|
119
|
-
await
|
|
119
|
+
await fs5.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
|
|
128
|
+
import fs6 from "fs-extra";
|
|
129
129
|
import path6 from "path";
|
|
130
130
|
async function registerRoute(projectRoot, key, routePath, ctx) {
|
|
131
131
|
const routesFile = path6.join(projectRoot, "src", "config", "routes.ts");
|
|
132
|
-
if (!await
|
|
133
|
-
let content = await
|
|
132
|
+
if (!await fs6.pathExists(routesFile)) return;
|
|
133
|
+
let content = await fs6.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 fs6.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
|
|
156
|
+
import fs7 from "fs-extra";
|
|
157
157
|
import path7 from "path";
|
|
158
158
|
async function generateBlog(projectRoot) {
|
|
159
159
|
const featureDir = path7.join(projectRoot, "src", "features", "blog");
|
|
160
|
-
if (await
|
|
160
|
+
if (await fs7.pathExists(featureDir)) {
|
|
161
161
|
log.error("Blog feature already exists at src/features/blog/");
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
@@ -180,8 +180,8 @@ async function generateBlog(projectRoot) {
|
|
|
180
180
|
for (const [filePath, content] of Object.entries(files)) {
|
|
181
181
|
const fullPath = path7.join(projectRoot, filePath);
|
|
182
182
|
ctx.trackCreatedFile(fullPath);
|
|
183
|
-
await
|
|
184
|
-
await
|
|
183
|
+
await fs7.ensureDir(path7.dirname(fullPath));
|
|
184
|
+
await fs7.writeFile(fullPath, content);
|
|
185
185
|
count++;
|
|
186
186
|
}
|
|
187
187
|
await addDependencies(projectRoot, {
|
|
@@ -209,11 +209,11 @@ async function generateBlog(projectRoot) {
|
|
|
209
209
|
}
|
|
210
210
|
async function setConfigFlag(projectRoot, ctx) {
|
|
211
211
|
const configPath = path7.join(projectRoot, "src", "config", "app.config.ts");
|
|
212
|
-
if (!await
|
|
212
|
+
if (!await fs7.pathExists(configPath)) return;
|
|
213
213
|
await ctx.trackModifiedFile(configPath);
|
|
214
|
-
const content = await
|
|
214
|
+
const content = await fs7.readFile(configPath, "utf-8");
|
|
215
215
|
const updated = content.replace(/blog:\s*false/, "blog: true");
|
|
216
|
-
await
|
|
216
|
+
await fs7.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
|
|
735
|
+
import fs8 from "fs-extra";
|
|
736
736
|
import path8 from "path";
|
|
737
737
|
async function generateDarkMode(projectRoot) {
|
|
738
738
|
const featureDir = path8.join(projectRoot, "src", "features", "dark-mode");
|
|
739
|
-
if (await
|
|
739
|
+
if (await fs8.pathExists(featureDir)) {
|
|
740
740
|
log.error("Dark mode feature already exists at src/features/dark-mode/");
|
|
741
741
|
return;
|
|
742
742
|
}
|
|
@@ -753,8 +753,8 @@ async function generateDarkMode(projectRoot) {
|
|
|
753
753
|
for (const [filePath, content] of Object.entries(files)) {
|
|
754
754
|
const fullPath = path8.join(projectRoot, filePath);
|
|
755
755
|
ctx.trackCreatedFile(fullPath);
|
|
756
|
-
await
|
|
757
|
-
await
|
|
756
|
+
await fs8.ensureDir(path8.dirname(fullPath));
|
|
757
|
+
await fs8.writeFile(fullPath, content);
|
|
758
758
|
count++;
|
|
759
759
|
}
|
|
760
760
|
await wireLayout(projectRoot, ctx);
|
|
@@ -785,17 +785,17 @@ async function generateDarkMode(projectRoot) {
|
|
|
785
785
|
}
|
|
786
786
|
async function setConfigFlag2(projectRoot, ctx) {
|
|
787
787
|
const configPath = path8.join(projectRoot, "src", "config", "app.config.ts");
|
|
788
|
-
if (!await
|
|
788
|
+
if (!await fs8.pathExists(configPath)) return;
|
|
789
789
|
await ctx.trackModifiedFile(configPath);
|
|
790
|
-
const content = await
|
|
790
|
+
const content = await fs8.readFile(configPath, "utf-8");
|
|
791
791
|
const updated = content.replace(/darkMode:\s*false/, "darkMode: true");
|
|
792
|
-
await
|
|
792
|
+
await fs8.writeFile(configPath, updated);
|
|
793
793
|
}
|
|
794
794
|
async function wireLayout(projectRoot, ctx) {
|
|
795
795
|
const layoutPath = path8.join(projectRoot, "src", "app", "layout.tsx");
|
|
796
|
-
if (!await
|
|
796
|
+
if (!await fs8.pathExists(layoutPath)) return;
|
|
797
797
|
await ctx.trackModifiedFile(layoutPath);
|
|
798
|
-
let content = await
|
|
798
|
+
let content = await fs8.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 fs8.writeFile(layoutPath, content);
|
|
813
813
|
}
|
|
814
814
|
async function wireProviders(projectRoot, ctx) {
|
|
815
815
|
const providersPath = path8.join(projectRoot, "src", "app", "providers.tsx");
|
|
816
|
-
if (!await
|
|
816
|
+
if (!await fs8.pathExists(providersPath)) return;
|
|
817
817
|
await ctx.trackModifiedFile(providersPath);
|
|
818
|
-
let content = await
|
|
818
|
+
let content = await fs8.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 fs8.writeFile(providersPath, content);
|
|
838
838
|
}
|
|
839
839
|
async function wireProtectedNav(projectRoot, ctx) {
|
|
840
840
|
const navPath = path8.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
841
|
-
if (!await
|
|
841
|
+
if (!await fs8.pathExists(navPath)) return;
|
|
842
842
|
await ctx.trackModifiedFile(navPath);
|
|
843
|
-
let content = await
|
|
843
|
+
let content = await fs8.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 fs8.writeFile(navPath, content);
|
|
859
859
|
}
|
|
860
860
|
async function wireTailwindDarkMode(projectRoot, ctx) {
|
|
861
861
|
const globalsPath = path8.join(projectRoot, "src", "styles", "globals.css");
|
|
862
|
-
if (!await
|
|
862
|
+
if (!await fs8.pathExists(globalsPath)) return;
|
|
863
863
|
await ctx.trackModifiedFile(globalsPath);
|
|
864
|
-
let content = await
|
|
864
|
+
let content = await fs8.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 fs8.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
|
|
1150
|
+
import fs9 from "fs-extra";
|
|
1151
1151
|
import path9 from "path";
|
|
1152
1152
|
async function addUserRelation(projectRoot, field, ctx) {
|
|
1153
1153
|
const authPath = path9.join(projectRoot, "prisma", "schema", "auth.prisma");
|
|
1154
|
-
if (!await
|
|
1154
|
+
if (!await fs9.pathExists(authPath)) return;
|
|
1155
1155
|
await ctx.trackModifiedFile(authPath);
|
|
1156
|
-
let content = await
|
|
1156
|
+
let content = await fs9.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 fs9.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
|
|
1175
|
+
import fs10 from "fs-extra";
|
|
1176
1176
|
import path10 from "path";
|
|
1177
1177
|
async function generateNotifications(projectRoot) {
|
|
1178
1178
|
const featureDir = path10.join(projectRoot, "src", "features", "notifications");
|
|
1179
|
-
if (await
|
|
1179
|
+
if (await fs10.pathExists(featureDir)) {
|
|
1180
1180
|
log.error("Notifications feature already exists at src/features/notifications/");
|
|
1181
1181
|
return;
|
|
1182
1182
|
}
|
|
@@ -1201,8 +1201,8 @@ async function generateNotifications(projectRoot) {
|
|
|
1201
1201
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1202
1202
|
const fullPath = path10.join(projectRoot, filePath);
|
|
1203
1203
|
ctx.trackCreatedFile(fullPath);
|
|
1204
|
-
await
|
|
1205
|
-
await
|
|
1204
|
+
await fs10.ensureDir(path10.dirname(fullPath));
|
|
1205
|
+
await fs10.writeFile(fullPath, content);
|
|
1206
1206
|
count++;
|
|
1207
1207
|
}
|
|
1208
1208
|
await addUserRelation(projectRoot, "notifications Notification[]", ctx);
|
|
@@ -1230,9 +1230,9 @@ async function generateNotifications(projectRoot) {
|
|
|
1230
1230
|
}
|
|
1231
1231
|
async function wireProtectedNav2(projectRoot, ctx) {
|
|
1232
1232
|
const navPath = path10.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
1233
|
-
if (!await
|
|
1233
|
+
if (!await fs10.pathExists(navPath)) return;
|
|
1234
1234
|
await ctx.trackModifiedFile(navPath);
|
|
1235
|
-
let content = await
|
|
1235
|
+
let content = await fs10.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 fs10.writeFile(navPath, content);
|
|
1247
1247
|
}
|
|
1248
1248
|
async function setConfigFlag3(projectRoot, ctx) {
|
|
1249
1249
|
const configPath = path10.join(projectRoot, "src", "config", "app.config.ts");
|
|
1250
|
-
if (!await
|
|
1250
|
+
if (!await fs10.pathExists(configPath)) return;
|
|
1251
1251
|
await ctx.trackModifiedFile(configPath);
|
|
1252
|
-
const content = await
|
|
1252
|
+
const content = await fs10.readFile(configPath, "utf-8");
|
|
1253
1253
|
const updated = content.replace(/notifications:\s*false/, "notifications: true");
|
|
1254
|
-
await
|
|
1254
|
+
await fs10.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
|
|
1850
|
+
import fs11 from "fs-extra";
|
|
1851
1851
|
import path11 from "path";
|
|
1852
1852
|
async function generateAnalytics(projectRoot) {
|
|
1853
1853
|
const featureDir = path11.join(projectRoot, "src", "features", "analytics");
|
|
1854
|
-
if (await
|
|
1854
|
+
if (await fs11.pathExists(featureDir)) {
|
|
1855
1855
|
log.error("Analytics feature already exists at src/features/analytics/");
|
|
1856
1856
|
return;
|
|
1857
1857
|
}
|
|
@@ -1868,8 +1868,8 @@ async function generateAnalytics(projectRoot) {
|
|
|
1868
1868
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1869
1869
|
const fullPath = path11.join(projectRoot, filePath);
|
|
1870
1870
|
ctx.trackCreatedFile(fullPath);
|
|
1871
|
-
await
|
|
1872
|
-
await
|
|
1871
|
+
await fs11.ensureDir(path11.dirname(fullPath));
|
|
1872
|
+
await fs11.writeFile(fullPath, content);
|
|
1873
1873
|
count++;
|
|
1874
1874
|
}
|
|
1875
1875
|
await wireProviders2(projectRoot, ctx);
|
|
@@ -1901,9 +1901,9 @@ async function generateAnalytics(projectRoot) {
|
|
|
1901
1901
|
}
|
|
1902
1902
|
async function wireProviders2(projectRoot, ctx) {
|
|
1903
1903
|
const providersPath = path11.join(projectRoot, "src", "app", "providers.tsx");
|
|
1904
|
-
if (!await
|
|
1904
|
+
if (!await fs11.pathExists(providersPath)) return;
|
|
1905
1905
|
await ctx.trackModifiedFile(providersPath);
|
|
1906
|
-
let content = await
|
|
1906
|
+
let content = await fs11.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 fs11.writeFile(providersPath, content);
|
|
1922
|
+
const written = await fs11.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."
|
|
@@ -1928,11 +1928,11 @@ async function wireProviders2(projectRoot, ctx) {
|
|
|
1928
1928
|
}
|
|
1929
1929
|
async function setConfigFlag4(projectRoot, ctx) {
|
|
1930
1930
|
const configPath = path11.join(projectRoot, "src", "config", "app.config.ts");
|
|
1931
|
-
if (!await
|
|
1931
|
+
if (!await fs11.pathExists(configPath)) return;
|
|
1932
1932
|
await ctx.trackModifiedFile(configPath);
|
|
1933
|
-
const content = await
|
|
1933
|
+
const content = await fs11.readFile(configPath, "utf-8");
|
|
1934
1934
|
const updated = content.replace(/analytics:\s*false/, "analytics: true");
|
|
1935
|
-
await
|
|
1935
|
+
await fs11.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
|
|
2294
|
+
import fs12 from "fs-extra";
|
|
2295
2295
|
import path12 from "path";
|
|
2296
2296
|
async function generateCommandPalette(projectRoot) {
|
|
2297
2297
|
const featureDir = path12.join(projectRoot, "src", "features", "command-palette");
|
|
2298
|
-
if (await
|
|
2298
|
+
if (await fs12.pathExists(featureDir)) {
|
|
2299
2299
|
log.error("Command palette feature already exists at src/features/command-palette/");
|
|
2300
2300
|
return;
|
|
2301
2301
|
}
|
|
@@ -2315,8 +2315,8 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2315
2315
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2316
2316
|
const fullPath = path12.join(projectRoot, filePath);
|
|
2317
2317
|
ctx.trackCreatedFile(fullPath);
|
|
2318
|
-
await
|
|
2319
|
-
await
|
|
2318
|
+
await fs12.ensureDir(path12.dirname(fullPath));
|
|
2319
|
+
await fs12.writeFile(fullPath, content);
|
|
2320
2320
|
count++;
|
|
2321
2321
|
}
|
|
2322
2322
|
await addDependencies(projectRoot, { cmdk: "^1.0.0" });
|
|
@@ -2340,8 +2340,8 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2340
2340
|
}
|
|
2341
2341
|
async function wireLayout2(projectRoot, ctx) {
|
|
2342
2342
|
const layoutPath = path12.join(projectRoot, "src", "app", "layout.tsx");
|
|
2343
|
-
if (!await
|
|
2344
|
-
let content = await
|
|
2343
|
+
if (!await fs12.pathExists(layoutPath)) return;
|
|
2344
|
+
let content = await fs12.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 fs12.writeFile(layoutPath, content);
|
|
2354
2354
|
}
|
|
2355
2355
|
async function setConfigFlag5(projectRoot, ctx) {
|
|
2356
2356
|
const configPath = path12.join(projectRoot, "src", "config", "app.config.ts");
|
|
2357
|
-
if (!await
|
|
2357
|
+
if (!await fs12.pathExists(configPath)) return;
|
|
2358
2358
|
await ctx.trackModifiedFile(configPath);
|
|
2359
|
-
const content = await
|
|
2359
|
+
const content = await fs12.readFile(configPath, "utf-8");
|
|
2360
2360
|
const updated = content.replace(/commandPalette:\s*false/, "commandPalette: true");
|
|
2361
|
-
await
|
|
2361
|
+
await fs12.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
|
|
2777
|
+
import fs13 from "fs-extra";
|
|
2778
2778
|
import path13 from "path";
|
|
2779
2779
|
async function generateOnboarding(projectRoot) {
|
|
2780
2780
|
const featureDir = path13.join(projectRoot, "src", "features", "onboarding");
|
|
2781
|
-
if (await
|
|
2781
|
+
if (await fs13.pathExists(featureDir)) {
|
|
2782
2782
|
log.error("Onboarding feature already exists at src/features/onboarding/");
|
|
2783
2783
|
return;
|
|
2784
2784
|
}
|
|
@@ -2802,8 +2802,8 @@ async function generateOnboarding(projectRoot) {
|
|
|
2802
2802
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2803
2803
|
const fullPath = path13.join(projectRoot, filePath);
|
|
2804
2804
|
ctx.trackCreatedFile(fullPath);
|
|
2805
|
-
await
|
|
2806
|
-
await
|
|
2805
|
+
await fs13.ensureDir(path13.dirname(fullPath));
|
|
2806
|
+
await fs13.writeFile(fullPath, content);
|
|
2807
2807
|
count++;
|
|
2808
2808
|
}
|
|
2809
2809
|
await addUserRelation(projectRoot, "onboardingProgress OnboardingProgress?", ctx);
|
|
@@ -2829,11 +2829,11 @@ async function generateOnboarding(projectRoot) {
|
|
|
2829
2829
|
}
|
|
2830
2830
|
async function setConfigFlag6(projectRoot, ctx) {
|
|
2831
2831
|
const configPath = path13.join(projectRoot, "src", "config", "app.config.ts");
|
|
2832
|
-
if (!await
|
|
2832
|
+
if (!await fs13.pathExists(configPath)) return;
|
|
2833
2833
|
await ctx.trackModifiedFile(configPath);
|
|
2834
|
-
const content = await
|
|
2834
|
+
const content = await fs13.readFile(configPath, "utf-8");
|
|
2835
2835
|
const updated = content.replace(/onboarding:\s*false/, "onboarding: true");
|
|
2836
|
-
await
|
|
2836
|
+
await fs13.writeFile(configPath, updated);
|
|
2837
2837
|
}
|
|
2838
2838
|
function prismaSchema2() {
|
|
2839
2839
|
return `// Generated by mars generate onboarding (onboarding@${GENERATOR_VERSION6})
|
|
@@ -3360,11 +3360,11 @@ var search_exports = {};
|
|
|
3360
3360
|
__export(search_exports, {
|
|
3361
3361
|
generateSearch: () => generateSearch
|
|
3362
3362
|
});
|
|
3363
|
-
import
|
|
3363
|
+
import fs14 from "fs-extra";
|
|
3364
3364
|
import path14 from "path";
|
|
3365
3365
|
async function generateSearch(projectRoot) {
|
|
3366
3366
|
const featureDir = path14.join(projectRoot, "src", "features", "search");
|
|
3367
|
-
if (await
|
|
3367
|
+
if (await fs14.pathExists(featureDir)) {
|
|
3368
3368
|
log.error("Search feature already exists at src/features/search/");
|
|
3369
3369
|
return;
|
|
3370
3370
|
}
|
|
@@ -3386,8 +3386,8 @@ async function generateSearch(projectRoot) {
|
|
|
3386
3386
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3387
3387
|
const fullPath = path14.join(projectRoot, filePath);
|
|
3388
3388
|
ctx.trackCreatedFile(fullPath);
|
|
3389
|
-
await
|
|
3390
|
-
await
|
|
3389
|
+
await fs14.ensureDir(path14.dirname(fullPath));
|
|
3390
|
+
await fs14.writeFile(fullPath, content);
|
|
3391
3391
|
count++;
|
|
3392
3392
|
}
|
|
3393
3393
|
await wireProtectedNav3(projectRoot, ctx);
|
|
@@ -3416,8 +3416,8 @@ async function generateSearch(projectRoot) {
|
|
|
3416
3416
|
}
|
|
3417
3417
|
async function wireProtectedNav3(projectRoot, ctx) {
|
|
3418
3418
|
const navPath = path14.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
3419
|
-
if (!await
|
|
3420
|
-
let content = await
|
|
3419
|
+
if (!await fs14.pathExists(navPath)) return;
|
|
3420
|
+
let content = await fs14.readFile(navPath, "utf-8");
|
|
3421
3421
|
if (content.includes("NavSearch")) return;
|
|
3422
3422
|
await ctx.trackModifiedFile(navPath);
|
|
3423
3423
|
content = insertImportAfterDirectives(
|
|
@@ -3429,15 +3429,15 @@ async function wireProtectedNav3(projectRoot, ctx) {
|
|
|
3429
3429
|
/(<div className="flex items-center gap-3">)\s*\n(\s*)(<)/,
|
|
3430
3430
|
"$1\n$2<NavSearch />\n$2$3"
|
|
3431
3431
|
);
|
|
3432
|
-
await
|
|
3432
|
+
await fs14.writeFile(navPath, content);
|
|
3433
3433
|
}
|
|
3434
3434
|
async function setConfigFlag7(projectRoot, ctx) {
|
|
3435
3435
|
const configPath = path14.join(projectRoot, "src", "config", "app.config.ts");
|
|
3436
|
-
if (!await
|
|
3436
|
+
if (!await fs14.pathExists(configPath)) return;
|
|
3437
3437
|
await ctx.trackModifiedFile(configPath);
|
|
3438
|
-
const content = await
|
|
3438
|
+
const content = await fs14.readFile(configPath, "utf-8");
|
|
3439
3439
|
const updated = content.replace(/search:\s*false/, "search: true");
|
|
3440
|
-
await
|
|
3440
|
+
await fs14.writeFile(configPath, updated);
|
|
3441
3441
|
}
|
|
3442
3442
|
function types6() {
|
|
3443
3443
|
return `${STAMP7}
|
|
@@ -3791,11 +3791,11 @@ var realtime_exports = {};
|
|
|
3791
3791
|
__export(realtime_exports, {
|
|
3792
3792
|
generateRealtime: () => generateRealtime
|
|
3793
3793
|
});
|
|
3794
|
-
import
|
|
3794
|
+
import fs15 from "fs-extra";
|
|
3795
3795
|
import path15 from "path";
|
|
3796
3796
|
async function generateRealtime(projectRoot) {
|
|
3797
3797
|
const featureDir = path15.join(projectRoot, "src", "features", "realtime");
|
|
3798
|
-
if (await
|
|
3798
|
+
if (await fs15.pathExists(featureDir)) {
|
|
3799
3799
|
log.error("Realtime feature already exists at src/features/realtime/");
|
|
3800
3800
|
return;
|
|
3801
3801
|
}
|
|
@@ -3814,8 +3814,8 @@ async function generateRealtime(projectRoot) {
|
|
|
3814
3814
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3815
3815
|
const fullPath = path15.join(projectRoot, filePath);
|
|
3816
3816
|
ctx.trackCreatedFile(fullPath);
|
|
3817
|
-
await
|
|
3818
|
-
await
|
|
3817
|
+
await fs15.ensureDir(path15.dirname(fullPath));
|
|
3818
|
+
await fs15.writeFile(fullPath, content);
|
|
3819
3819
|
count++;
|
|
3820
3820
|
}
|
|
3821
3821
|
await setConfigFlag8(projectRoot, ctx);
|
|
@@ -3841,11 +3841,11 @@ async function generateRealtime(projectRoot) {
|
|
|
3841
3841
|
}
|
|
3842
3842
|
async function setConfigFlag8(projectRoot, ctx) {
|
|
3843
3843
|
const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
|
|
3844
|
-
if (!await
|
|
3844
|
+
if (!await fs15.pathExists(configPath)) return;
|
|
3845
3845
|
await ctx.trackModifiedFile(configPath);
|
|
3846
|
-
const content = await
|
|
3846
|
+
const content = await fs15.readFile(configPath, "utf-8");
|
|
3847
3847
|
const updated = content.replace(/realtime:\s*false/, "realtime: true");
|
|
3848
|
-
await
|
|
3848
|
+
await fs15.writeFile(configPath, updated);
|
|
3849
3849
|
}
|
|
3850
3850
|
function types7() {
|
|
3851
3851
|
return `${STAMP8}
|
|
@@ -4154,11 +4154,11 @@ var ai_exports = {};
|
|
|
4154
4154
|
__export(ai_exports, {
|
|
4155
4155
|
generateAI: () => generateAI
|
|
4156
4156
|
});
|
|
4157
|
-
import
|
|
4157
|
+
import fs16 from "fs-extra";
|
|
4158
4158
|
import path16 from "path";
|
|
4159
4159
|
async function generateAI(projectRoot) {
|
|
4160
4160
|
const featureDir = path16.join(projectRoot, "src", "features", "ai");
|
|
4161
|
-
if (await
|
|
4161
|
+
if (await fs16.pathExists(featureDir)) {
|
|
4162
4162
|
log.error("AI feature already exists at src/features/ai/");
|
|
4163
4163
|
return;
|
|
4164
4164
|
}
|
|
@@ -4178,8 +4178,8 @@ async function generateAI(projectRoot) {
|
|
|
4178
4178
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4179
4179
|
const fullPath = path16.join(projectRoot, filePath);
|
|
4180
4180
|
ctx.trackCreatedFile(fullPath);
|
|
4181
|
-
await
|
|
4182
|
-
await
|
|
4181
|
+
await fs16.ensureDir(path16.dirname(fullPath));
|
|
4182
|
+
await fs16.writeFile(fullPath, content);
|
|
4183
4183
|
count++;
|
|
4184
4184
|
}
|
|
4185
4185
|
await addDependencies(projectRoot, {
|
|
@@ -4206,11 +4206,11 @@ async function generateAI(projectRoot) {
|
|
|
4206
4206
|
}
|
|
4207
4207
|
async function setConfigFlag9(projectRoot, ctx) {
|
|
4208
4208
|
const configPath = path16.join(projectRoot, "src", "config", "app.config.ts");
|
|
4209
|
-
if (!await
|
|
4209
|
+
if (!await fs16.pathExists(configPath)) return;
|
|
4210
4210
|
await ctx.trackModifiedFile(configPath);
|
|
4211
|
-
const content = await
|
|
4211
|
+
const content = await fs16.readFile(configPath, "utf-8");
|
|
4212
4212
|
const updated = content.replace(/ai:\s*false/, "ai: true");
|
|
4213
|
-
await
|
|
4213
|
+
await fs16.writeFile(configPath, updated);
|
|
4214
4214
|
}
|
|
4215
4215
|
function types8() {
|
|
4216
4216
|
return `${STAMP9}
|
|
@@ -4649,11 +4649,11 @@ var cookie_consent_exports = {};
|
|
|
4649
4649
|
__export(cookie_consent_exports, {
|
|
4650
4650
|
generateCookieConsent: () => generateCookieConsent
|
|
4651
4651
|
});
|
|
4652
|
-
import
|
|
4652
|
+
import fs17 from "fs-extra";
|
|
4653
4653
|
import path17 from "path";
|
|
4654
4654
|
async function generateCookieConsent(projectRoot) {
|
|
4655
4655
|
const featureDir = path17.join(projectRoot, "src", "features", "cookie-consent");
|
|
4656
|
-
if (await
|
|
4656
|
+
if (await fs17.pathExists(featureDir)) {
|
|
4657
4657
|
log.error("Cookie consent feature already exists at src/features/cookie-consent/");
|
|
4658
4658
|
return;
|
|
4659
4659
|
}
|
|
@@ -4671,8 +4671,8 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4671
4671
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4672
4672
|
const fullPath = path17.join(projectRoot, filePath);
|
|
4673
4673
|
ctx.trackCreatedFile(fullPath);
|
|
4674
|
-
await
|
|
4675
|
-
await
|
|
4674
|
+
await fs17.ensureDir(path17.dirname(fullPath));
|
|
4675
|
+
await fs17.writeFile(fullPath, content);
|
|
4676
4676
|
count++;
|
|
4677
4677
|
}
|
|
4678
4678
|
await wireLayout3(projectRoot, ctx);
|
|
@@ -4698,8 +4698,8 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4698
4698
|
}
|
|
4699
4699
|
async function wireLayout3(projectRoot, ctx) {
|
|
4700
4700
|
const layoutPath = path17.join(projectRoot, "src", "app", "layout.tsx");
|
|
4701
|
-
if (!await
|
|
4702
|
-
let content = await
|
|
4701
|
+
if (!await fs17.pathExists(layoutPath)) return;
|
|
4702
|
+
let content = await fs17.readFile(layoutPath, "utf-8");
|
|
4703
4703
|
if (content.includes("CookieConsentBanner")) return;
|
|
4704
4704
|
await ctx.trackModifiedFile(layoutPath);
|
|
4705
4705
|
content = `import { CookieConsentBanner } from '@/features/cookie-consent';
|
|
@@ -4708,15 +4708,15 @@ ${content}`;
|
|
|
4708
4708
|
/(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
|
|
4709
4709
|
"$1$2\n$1<CookieConsentBanner />"
|
|
4710
4710
|
);
|
|
4711
|
-
await
|
|
4711
|
+
await fs17.writeFile(layoutPath, content);
|
|
4712
4712
|
}
|
|
4713
4713
|
async function setConfigFlag10(projectRoot, ctx) {
|
|
4714
4714
|
const configPath = path17.join(projectRoot, "src", "config", "app.config.ts");
|
|
4715
|
-
if (!await
|
|
4715
|
+
if (!await fs17.pathExists(configPath)) return;
|
|
4716
4716
|
await ctx.trackModifiedFile(configPath);
|
|
4717
|
-
const content = await
|
|
4717
|
+
const content = await fs17.readFile(configPath, "utf-8");
|
|
4718
4718
|
const updated = content.replace(/cookieConsent:\s*false/, "cookieConsent: true");
|
|
4719
|
-
await
|
|
4719
|
+
await fs17.writeFile(configPath, updated);
|
|
4720
4720
|
}
|
|
4721
4721
|
function types9() {
|
|
4722
4722
|
return `${STAMP10}
|
|
@@ -5001,11 +5001,11 @@ var coming_soon_exports = {};
|
|
|
5001
5001
|
__export(coming_soon_exports, {
|
|
5002
5002
|
generateComingSoon: () => generateComingSoon
|
|
5003
5003
|
});
|
|
5004
|
-
import
|
|
5004
|
+
import fs18 from "fs-extra";
|
|
5005
5005
|
import path18 from "path";
|
|
5006
5006
|
async function generateComingSoon(projectRoot) {
|
|
5007
5007
|
const featureDir = path18.join(projectRoot, "src", "features", "coming-soon");
|
|
5008
|
-
if (await
|
|
5008
|
+
if (await fs18.pathExists(featureDir)) {
|
|
5009
5009
|
log.error("Coming soon feature already exists at src/features/coming-soon/");
|
|
5010
5010
|
return;
|
|
5011
5011
|
}
|
|
@@ -5024,8 +5024,8 @@ async function generateComingSoon(projectRoot) {
|
|
|
5024
5024
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5025
5025
|
const fullPath = path18.join(projectRoot, filePath);
|
|
5026
5026
|
ctx.trackCreatedFile(fullPath);
|
|
5027
|
-
await
|
|
5028
|
-
await
|
|
5027
|
+
await fs18.ensureDir(path18.dirname(fullPath));
|
|
5028
|
+
await fs18.writeFile(fullPath, content);
|
|
5029
5029
|
count++;
|
|
5030
5030
|
}
|
|
5031
5031
|
await registerRoute(projectRoot, "comingSoon", "/coming-soon", ctx);
|
|
@@ -5048,11 +5048,11 @@ async function generateComingSoon(projectRoot) {
|
|
|
5048
5048
|
}
|
|
5049
5049
|
async function setConfigFlag11(projectRoot, ctx) {
|
|
5050
5050
|
const configPath = path18.join(projectRoot, "src", "config", "app.config.ts");
|
|
5051
|
-
if (!await
|
|
5051
|
+
if (!await fs18.pathExists(configPath)) return;
|
|
5052
5052
|
await ctx.trackModifiedFile(configPath);
|
|
5053
|
-
const content = await
|
|
5053
|
+
const content = await fs18.readFile(configPath, "utf-8");
|
|
5054
5054
|
const updated = content.replace(/comingSoon:\s*false/, "comingSoon: true");
|
|
5055
|
-
await
|
|
5055
|
+
await fs18.writeFile(configPath, updated);
|
|
5056
5056
|
}
|
|
5057
5057
|
function types10() {
|
|
5058
5058
|
return `${STAMP11}
|
|
@@ -5240,11 +5240,11 @@ var sentry_exports = {};
|
|
|
5240
5240
|
__export(sentry_exports, {
|
|
5241
5241
|
generateSentry: () => generateSentry
|
|
5242
5242
|
});
|
|
5243
|
-
import
|
|
5243
|
+
import fs19 from "fs-extra";
|
|
5244
5244
|
import path19 from "path";
|
|
5245
5245
|
async function generateSentry(projectRoot) {
|
|
5246
5246
|
const featureDir = path19.join(projectRoot, "src", "features", "sentry");
|
|
5247
|
-
if (await
|
|
5247
|
+
if (await fs19.pathExists(featureDir)) {
|
|
5248
5248
|
log.error("Sentry feature already exists at src/features/sentry/");
|
|
5249
5249
|
return;
|
|
5250
5250
|
}
|
|
@@ -5262,8 +5262,8 @@ async function generateSentry(projectRoot) {
|
|
|
5262
5262
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5263
5263
|
const fullPath = path19.join(projectRoot, filePath);
|
|
5264
5264
|
ctx.trackCreatedFile(fullPath);
|
|
5265
|
-
await
|
|
5266
|
-
await
|
|
5265
|
+
await fs19.ensureDir(path19.dirname(fullPath));
|
|
5266
|
+
await fs19.writeFile(fullPath, content);
|
|
5267
5267
|
count++;
|
|
5268
5268
|
}
|
|
5269
5269
|
await addDependencies(projectRoot, { "@sentry/nextjs": "^8.0.0" });
|
|
@@ -5290,8 +5290,8 @@ async function generateSentry(projectRoot) {
|
|
|
5290
5290
|
}
|
|
5291
5291
|
async function wireRootLayout(projectRoot, ctx) {
|
|
5292
5292
|
const layoutPath = path19.join(projectRoot, "src", "app", "layout.tsx");
|
|
5293
|
-
if (!await
|
|
5294
|
-
let content = await
|
|
5293
|
+
if (!await fs19.pathExists(layoutPath)) return;
|
|
5294
|
+
let content = await fs19.readFile(layoutPath, "utf-8");
|
|
5295
5295
|
if (content.includes("ErrorBoundary")) return;
|
|
5296
5296
|
await ctx.trackModifiedFile(layoutPath);
|
|
5297
5297
|
content = `import { ErrorBoundary } from '@/features/sentry';
|
|
@@ -5300,15 +5300,15 @@ ${content}`;
|
|
|
5300
5300
|
/(<Providers>)\{children\}(<\/Providers>)/,
|
|
5301
5301
|
"$1<ErrorBoundary>{children}</ErrorBoundary>$2"
|
|
5302
5302
|
);
|
|
5303
|
-
await
|
|
5303
|
+
await fs19.writeFile(layoutPath, content);
|
|
5304
5304
|
}
|
|
5305
5305
|
async function setConfigFlag12(projectRoot, ctx) {
|
|
5306
5306
|
const configPath = path19.join(projectRoot, "src", "config", "app.config.ts");
|
|
5307
|
-
if (!await
|
|
5307
|
+
if (!await fs19.pathExists(configPath)) return;
|
|
5308
5308
|
await ctx.trackModifiedFile(configPath);
|
|
5309
|
-
const content = await
|
|
5309
|
+
const content = await fs19.readFile(configPath, "utf-8");
|
|
5310
5310
|
const updated = content.replace(/sentry:\s*false/, "sentry: true");
|
|
5311
|
-
await
|
|
5311
|
+
await fs19.writeFile(configPath, updated);
|
|
5312
5312
|
}
|
|
5313
5313
|
function types11() {
|
|
5314
5314
|
return `${STAMP12}
|
|
@@ -5453,11 +5453,11 @@ var feature_flags_exports = {};
|
|
|
5453
5453
|
__export(feature_flags_exports, {
|
|
5454
5454
|
generateFeatureFlags: () => generateFeatureFlags
|
|
5455
5455
|
});
|
|
5456
|
-
import
|
|
5456
|
+
import fs20 from "fs-extra";
|
|
5457
5457
|
import path20 from "path";
|
|
5458
5458
|
async function generateFeatureFlags(projectRoot) {
|
|
5459
5459
|
const featureDir = path20.join(projectRoot, "src", "features", "feature-flags");
|
|
5460
|
-
if (await
|
|
5460
|
+
if (await fs20.pathExists(featureDir)) {
|
|
5461
5461
|
log.error("Feature flags feature already exists at src/features/feature-flags/");
|
|
5462
5462
|
return;
|
|
5463
5463
|
}
|
|
@@ -5478,8 +5478,8 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5478
5478
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5479
5479
|
const fullPath = path20.join(projectRoot, filePath);
|
|
5480
5480
|
ctx.trackCreatedFile(fullPath);
|
|
5481
|
-
await
|
|
5482
|
-
await
|
|
5481
|
+
await fs20.ensureDir(path20.dirname(fullPath));
|
|
5482
|
+
await fs20.writeFile(fullPath, content);
|
|
5483
5483
|
count++;
|
|
5484
5484
|
}
|
|
5485
5485
|
await setConfigFlag13(projectRoot, ctx);
|
|
@@ -5500,11 +5500,11 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5500
5500
|
}
|
|
5501
5501
|
async function setConfigFlag13(projectRoot, ctx) {
|
|
5502
5502
|
const configPath = path20.join(projectRoot, "src", "config", "app.config.ts");
|
|
5503
|
-
if (!await
|
|
5503
|
+
if (!await fs20.pathExists(configPath)) return;
|
|
5504
5504
|
await ctx.trackModifiedFile(configPath);
|
|
5505
|
-
const content = await
|
|
5505
|
+
const content = await fs20.readFile(configPath, "utf-8");
|
|
5506
5506
|
const updated = content.replace(/featureFlags:\s*false/, "featureFlags: true");
|
|
5507
|
-
await
|
|
5507
|
+
await fs20.writeFile(configPath, updated);
|
|
5508
5508
|
}
|
|
5509
5509
|
function types12() {
|
|
5510
5510
|
return `${STAMP13}
|
|
@@ -5746,6 +5746,7 @@ import { Command } from "commander";
|
|
|
5746
5746
|
import { createRequire } from "module";
|
|
5747
5747
|
import path from "path";
|
|
5748
5748
|
import { fileURLToPath } from "url";
|
|
5749
|
+
import fs from "fs-extra";
|
|
5749
5750
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5750
5751
|
function readVersionFromPackageJson(packageDir) {
|
|
5751
5752
|
const require2 = createRequire(import.meta.url);
|
|
@@ -5754,13 +5755,23 @@ function readVersionFromPackageJson(packageDir) {
|
|
|
5754
5755
|
return v ? v : "0.0.0";
|
|
5755
5756
|
}
|
|
5756
5757
|
function getCliVersion() {
|
|
5757
|
-
const
|
|
5758
|
-
|
|
5758
|
+
const candidates = [
|
|
5759
|
+
// Built bundle: __dirname = packages/cli/dist/
|
|
5760
|
+
path.resolve(__dirname, ".."),
|
|
5761
|
+
// Source (vitest): __dirname = packages/cli/src/utils/
|
|
5762
|
+
path.resolve(__dirname, "..", "..")
|
|
5763
|
+
];
|
|
5764
|
+
for (const candidate of candidates) {
|
|
5765
|
+
if (fs.existsSync(path.join(candidate, "package.json"))) {
|
|
5766
|
+
return readVersionFromPackageJson(candidate);
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
return "0.0.0";
|
|
5759
5770
|
}
|
|
5760
5771
|
|
|
5761
5772
|
// src/commands/create.ts
|
|
5762
5773
|
init_logger();
|
|
5763
|
-
import
|
|
5774
|
+
import fs22 from "fs-extra";
|
|
5764
5775
|
import path22 from "path";
|
|
5765
5776
|
import os3 from "os";
|
|
5766
5777
|
import ora from "ora";
|
|
@@ -5770,7 +5781,7 @@ import prompts5 from "prompts";
|
|
|
5770
5781
|
// src/utils/template.ts
|
|
5771
5782
|
import path2 from "path";
|
|
5772
5783
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5773
|
-
import
|
|
5784
|
+
import fs2 from "fs-extra";
|
|
5774
5785
|
var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
5775
5786
|
function getTemplatePath() {
|
|
5776
5787
|
const candidates = [
|
|
@@ -5781,7 +5792,7 @@ function getTemplatePath() {
|
|
|
5781
5792
|
path2.resolve(__dirname2, "..", "..", "..", "..", "template")
|
|
5782
5793
|
];
|
|
5783
5794
|
for (const candidate of candidates) {
|
|
5784
|
-
if (
|
|
5795
|
+
if (fs2.existsSync(candidate)) {
|
|
5785
5796
|
return candidate;
|
|
5786
5797
|
}
|
|
5787
5798
|
}
|
|
@@ -6187,7 +6198,7 @@ async function promptTheme() {
|
|
|
6187
6198
|
|
|
6188
6199
|
// src/generators/scaffold.ts
|
|
6189
6200
|
init_logger();
|
|
6190
|
-
import
|
|
6201
|
+
import fs3 from "fs-extra";
|
|
6191
6202
|
import path3 from "path";
|
|
6192
6203
|
import { generateBrandCss } from "@mars-stack/ui/utils";
|
|
6193
6204
|
|
|
@@ -6372,17 +6383,17 @@ var IGNORED_FILES = [
|
|
|
6372
6383
|
async function copyTemplateFiles(templateDir, targetDir) {
|
|
6373
6384
|
let fileCount = 0;
|
|
6374
6385
|
async function walkAndCopy(src, dest) {
|
|
6375
|
-
const entries = await
|
|
6386
|
+
const entries = await fs3.readdir(src, { withFileTypes: true });
|
|
6376
6387
|
for (const entry of entries) {
|
|
6377
6388
|
const srcPath = path3.join(src, entry.name);
|
|
6378
6389
|
const destPath = path3.join(dest, entry.name);
|
|
6379
6390
|
if (entry.isDirectory()) {
|
|
6380
6391
|
if (IGNORED_DIRS.includes(entry.name)) continue;
|
|
6381
|
-
await
|
|
6392
|
+
await fs3.ensureDir(destPath);
|
|
6382
6393
|
await walkAndCopy(srcPath, destPath);
|
|
6383
6394
|
} else {
|
|
6384
6395
|
if (IGNORED_FILES.includes(entry.name)) continue;
|
|
6385
|
-
await
|
|
6396
|
+
await fs3.copy(srcPath, destPath);
|
|
6386
6397
|
fileCount++;
|
|
6387
6398
|
}
|
|
6388
6399
|
}
|
|
@@ -6397,15 +6408,15 @@ function resolveMarsPackageRange(packageName, templateDir) {
|
|
|
6397
6408
|
path3.resolve(templateDir, "..", "..", "packages", shortName, "package.json")
|
|
6398
6409
|
];
|
|
6399
6410
|
for (const candidate of candidates) {
|
|
6400
|
-
if (
|
|
6401
|
-
const pkg =
|
|
6411
|
+
if (fs3.existsSync(candidate)) {
|
|
6412
|
+
const pkg = fs3.readJsonSync(candidate);
|
|
6402
6413
|
if (typeof pkg.version === "string") {
|
|
6403
6414
|
const [major, minor] = pkg.version.split(".");
|
|
6404
6415
|
return `^${major}.${minor}.0`;
|
|
6405
6416
|
}
|
|
6406
6417
|
}
|
|
6407
6418
|
}
|
|
6408
|
-
const templatePkg =
|
|
6419
|
+
const templatePkg = fs3.readJsonSync(path3.join(templateDir, "package.json"));
|
|
6409
6420
|
const existing = templatePkg.dependencies?.[packageName];
|
|
6410
6421
|
if (existing && existing !== "*") {
|
|
6411
6422
|
if (/^[\^~>=]/.test(existing)) return existing;
|
|
@@ -6417,7 +6428,7 @@ function resolveMarsPackageRange(packageName, templateDir) {
|
|
|
6417
6428
|
function generatePackageJson(config) {
|
|
6418
6429
|
const templateDir = getTemplatePath();
|
|
6419
6430
|
const templatePkg = path3.join(templateDir, "package.json");
|
|
6420
|
-
const pkg =
|
|
6431
|
+
const pkg = fs3.readJsonSync(templatePkg);
|
|
6421
6432
|
pkg.name = config.name;
|
|
6422
6433
|
pkg.version = "0.1.0";
|
|
6423
6434
|
pkg.private = true;
|
|
@@ -6617,8 +6628,8 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6617
6628
|
if (flagValue !== false) continue;
|
|
6618
6629
|
for (const relativePath of paths) {
|
|
6619
6630
|
const fullPath = path3.join(targetDir, relativePath);
|
|
6620
|
-
if (await
|
|
6621
|
-
await
|
|
6631
|
+
if (await fs3.pathExists(fullPath)) {
|
|
6632
|
+
await fs3.remove(fullPath);
|
|
6622
6633
|
log.step(`Pruned disabled feature path: ${relativePath}`);
|
|
6623
6634
|
}
|
|
6624
6635
|
}
|
|
@@ -6628,42 +6639,42 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6628
6639
|
}
|
|
6629
6640
|
if (relationsToRemove.length > 0) {
|
|
6630
6641
|
const authPath = path3.join(targetDir, "prisma", "schema", "auth.prisma");
|
|
6631
|
-
if (await
|
|
6632
|
-
let content = await
|
|
6642
|
+
if (await fs3.pathExists(authPath)) {
|
|
6643
|
+
let content = await fs3.readFile(authPath, "utf-8");
|
|
6633
6644
|
for (const field of relationsToRemove) {
|
|
6634
6645
|
content = content.replace(new RegExp(`\\s*${field}\\s+\\S+.*\\n`, "g"), "\n");
|
|
6635
6646
|
}
|
|
6636
|
-
await
|
|
6647
|
+
await fs3.writeFile(authPath, content);
|
|
6637
6648
|
}
|
|
6638
6649
|
}
|
|
6639
6650
|
}
|
|
6640
6651
|
async function scaffoldProject(targetDir, config) {
|
|
6641
6652
|
const templateDir = getTemplatePath();
|
|
6642
|
-
if (!await
|
|
6653
|
+
if (!await fs3.pathExists(templateDir)) {
|
|
6643
6654
|
throw new Error(`Template directory not found: ${templateDir}`);
|
|
6644
6655
|
}
|
|
6645
|
-
await
|
|
6656
|
+
await fs3.ensureDir(targetDir);
|
|
6646
6657
|
const fileCount = await copyTemplateFiles(templateDir, targetDir);
|
|
6647
6658
|
await pruneDisabledFeatures(config.features, targetDir);
|
|
6648
|
-
await
|
|
6649
|
-
await
|
|
6659
|
+
await fs3.writeFile(path3.join(targetDir, "package.json"), generatePackageJson(config));
|
|
6660
|
+
await fs3.writeFile(
|
|
6650
6661
|
path3.join(targetDir, "src", "config", "app.config.ts"),
|
|
6651
6662
|
generateAppConfig(config)
|
|
6652
6663
|
);
|
|
6653
|
-
await
|
|
6654
|
-
await
|
|
6655
|
-
await
|
|
6656
|
-
await
|
|
6657
|
-
await
|
|
6664
|
+
await fs3.writeFile(path3.join(targetDir, "docker-compose.yml"), generateDockerCompose(config));
|
|
6665
|
+
await fs3.writeFile(path3.join(targetDir, "src", "app", "layout.tsx"), generateLayout(config));
|
|
6666
|
+
await fs3.writeFile(path3.join(targetDir, ".env"), generateEnvFile(config));
|
|
6667
|
+
await fs3.writeFile(path3.join(targetDir, ".env.example"), generateEnvExample(config));
|
|
6668
|
+
await fs3.writeFile(path3.join(targetDir, ".gitignore"), generateGitignore());
|
|
6658
6669
|
await patchGlobalsCssForScaffoldedProject(targetDir, config);
|
|
6659
6670
|
await generateBrandCssForProject(targetDir, config);
|
|
6660
6671
|
return { fileCount };
|
|
6661
6672
|
}
|
|
6662
6673
|
async function generateBrandCssForProject(targetDir, config) {
|
|
6663
6674
|
const brandPath = path3.join(targetDir, "src", "styles", "brand.css");
|
|
6664
|
-
if (!await
|
|
6675
|
+
if (!await fs3.pathExists(brandPath)) return;
|
|
6665
6676
|
const css = generateBrandCss(config.theme.primaryColor);
|
|
6666
|
-
await
|
|
6677
|
+
await fs3.writeFile(brandPath, css);
|
|
6667
6678
|
}
|
|
6668
6679
|
var VALID_DESIGN_DIRECTIONS = [
|
|
6669
6680
|
"modern-saas",
|
|
@@ -6674,8 +6685,8 @@ var VALID_DESIGN_DIRECTIONS = [
|
|
|
6674
6685
|
];
|
|
6675
6686
|
async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
6676
6687
|
const globalsPath = path3.join(targetDir, "src", "styles", "globals.css");
|
|
6677
|
-
if (!await
|
|
6678
|
-
let content = await
|
|
6688
|
+
if (!await fs3.pathExists(globalsPath)) return;
|
|
6689
|
+
let content = await fs3.readFile(globalsPath, "utf-8");
|
|
6679
6690
|
content = content.replace(
|
|
6680
6691
|
'@source "../../../node_modules/@mars-stack/ui/dist/**/*.js";',
|
|
6681
6692
|
'@source "../../node_modules/@mars-stack/ui/dist/**/*.js";'
|
|
@@ -6694,7 +6705,7 @@ async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
|
6694
6705
|
${newImport}`
|
|
6695
6706
|
);
|
|
6696
6707
|
}
|
|
6697
|
-
await
|
|
6708
|
+
await fs3.writeFile(globalsPath, content);
|
|
6698
6709
|
}
|
|
6699
6710
|
|
|
6700
6711
|
// src/generators/generate-selected.ts
|
|
@@ -6810,13 +6821,13 @@ async function generateSelectedFeatures(projectRoot, features) {
|
|
|
6810
6821
|
}
|
|
6811
6822
|
|
|
6812
6823
|
// src/utils/telemetry.ts
|
|
6813
|
-
import
|
|
6824
|
+
import fs21 from "fs-extra";
|
|
6814
6825
|
import path21 from "path";
|
|
6815
6826
|
import os2 from "os";
|
|
6816
6827
|
var RC_PATH = path21.join(os2.homedir(), ".marsrc");
|
|
6817
6828
|
function isTelemetryEnabled() {
|
|
6818
6829
|
try {
|
|
6819
|
-
const config =
|
|
6830
|
+
const config = fs21.readJsonSync(RC_PATH);
|
|
6820
6831
|
return config.enabled === true;
|
|
6821
6832
|
} catch {
|
|
6822
6833
|
return false;
|
|
@@ -6825,16 +6836,16 @@ function isTelemetryEnabled() {
|
|
|
6825
6836
|
function enableTelemetry() {
|
|
6826
6837
|
const config = loadOrCreateConfig();
|
|
6827
6838
|
config.enabled = true;
|
|
6828
|
-
|
|
6839
|
+
fs21.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
6829
6840
|
}
|
|
6830
6841
|
function disableTelemetry() {
|
|
6831
6842
|
const config = loadOrCreateConfig();
|
|
6832
6843
|
config.enabled = false;
|
|
6833
|
-
|
|
6844
|
+
fs21.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
6834
6845
|
}
|
|
6835
6846
|
function loadOrCreateConfig() {
|
|
6836
6847
|
try {
|
|
6837
|
-
return
|
|
6848
|
+
return fs21.readJsonSync(RC_PATH);
|
|
6838
6849
|
} catch {
|
|
6839
6850
|
return { enabled: false, anonymousId: crypto.randomUUID() };
|
|
6840
6851
|
}
|
|
@@ -6855,7 +6866,7 @@ function trackEvent(event, properties) {
|
|
|
6855
6866
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6856
6867
|
};
|
|
6857
6868
|
const logPath = path21.join(os2.homedir(), ".mars-telemetry.log");
|
|
6858
|
-
|
|
6869
|
+
fs21.appendFile(logPath, JSON.stringify(payload) + "\n").catch(() => {
|
|
6859
6870
|
});
|
|
6860
6871
|
}
|
|
6861
6872
|
|
|
@@ -6887,8 +6898,8 @@ async function createCommand(projectName, options) {
|
|
|
6887
6898
|
const projectInfo = useDefaults ? getDefaultProjectInfo(projectName) : await promptProjectInfo(projectName);
|
|
6888
6899
|
if (!projectInfo) return;
|
|
6889
6900
|
const targetDir = resolveProjectPath(projectInfo.name);
|
|
6890
|
-
if (await
|
|
6891
|
-
const entries = await
|
|
6901
|
+
if (await fs22.pathExists(targetDir)) {
|
|
6902
|
+
const entries = await fs22.readdir(targetDir);
|
|
6892
6903
|
if (entries.length > 0) {
|
|
6893
6904
|
log.error(`Directory "${projectInfo.name}" already exists and is not empty.`);
|
|
6894
6905
|
return;
|
|
@@ -6929,7 +6940,7 @@ async function createCommand(projectName, options) {
|
|
|
6929
6940
|
log.info("Scaffolding cancelled.");
|
|
6930
6941
|
process.exit(0);
|
|
6931
6942
|
}
|
|
6932
|
-
if (!
|
|
6943
|
+
if (!fs22.pathExistsSync(RC_PATH2)) {
|
|
6933
6944
|
const { telemetryOptIn } = await prompts5(
|
|
6934
6945
|
{
|
|
6935
6946
|
type: "confirm",
|
|
@@ -6968,10 +6979,10 @@ async function createCommand(projectName, options) {
|
|
|
6968
6979
|
} catch (err) {
|
|
6969
6980
|
spinner.fail("Failed to scaffold project");
|
|
6970
6981
|
log.error(err instanceof Error ? err.message : String(err));
|
|
6971
|
-
if (await
|
|
6982
|
+
if (await fs22.pathExists(targetDir)) {
|
|
6972
6983
|
const cleanupSpinner = ora("Cleaning up...").start();
|
|
6973
6984
|
try {
|
|
6974
|
-
await
|
|
6985
|
+
await fs22.remove(targetDir);
|
|
6975
6986
|
cleanupSpinner.succeed("Cleaned up partial scaffold");
|
|
6976
6987
|
} catch {
|
|
6977
6988
|
cleanupSpinner.warn(`Could not clean up ${targetDir}. You may need to remove it manually.`);
|
|
@@ -6986,7 +6997,7 @@ function countEnabled(flags) {
|
|
|
6986
6997
|
|
|
6987
6998
|
// src/commands/doctor.ts
|
|
6988
6999
|
init_logger();
|
|
6989
|
-
import
|
|
7000
|
+
import fs23 from "fs-extra";
|
|
6990
7001
|
import path23 from "path";
|
|
6991
7002
|
import { execSync } from "child_process";
|
|
6992
7003
|
function commandExists(cmd) {
|
|
@@ -7008,11 +7019,11 @@ async function checkForUpgrades() {
|
|
|
7008
7019
|
log.blank();
|
|
7009
7020
|
log.title("Upgrade Check");
|
|
7010
7021
|
const packageJsonPath = path23.join(process.cwd(), "package.json");
|
|
7011
|
-
if (!
|
|
7022
|
+
if (!fs23.pathExistsSync(packageJsonPath)) {
|
|
7012
7023
|
log.warn("No package.json found \u2014 skipping upgrade check.");
|
|
7013
7024
|
return;
|
|
7014
7025
|
}
|
|
7015
|
-
const packageJson =
|
|
7026
|
+
const packageJson = fs23.readJsonSync(packageJsonPath);
|
|
7016
7027
|
const deps = packageJson.dependencies ?? {};
|
|
7017
7028
|
const devDeps = packageJson.devDependencies ?? {};
|
|
7018
7029
|
const currentRaw = deps["@mars-stack/core"] ?? devDeps["@mars-stack/core"];
|
|
@@ -7074,25 +7085,25 @@ async function doctorCommand(options) {
|
|
|
7074
7085
|
},
|
|
7075
7086
|
{
|
|
7076
7087
|
name: "package.json exists",
|
|
7077
|
-
check: () =>
|
|
7088
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), "package.json"))
|
|
7078
7089
|
},
|
|
7079
7090
|
{
|
|
7080
7091
|
name: ".env file exists",
|
|
7081
|
-
check: () =>
|
|
7092
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), ".env"))
|
|
7082
7093
|
},
|
|
7083
7094
|
{
|
|
7084
7095
|
name: "Prisma schema exists",
|
|
7085
|
-
check: () =>
|
|
7096
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), "prisma", "schema")) || fs23.pathExistsSync(path23.join(process.cwd(), "prisma", "schema.prisma"))
|
|
7086
7097
|
},
|
|
7087
7098
|
{
|
|
7088
7099
|
name: "node_modules installed",
|
|
7089
|
-
check: () =>
|
|
7100
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), "node_modules"))
|
|
7090
7101
|
},
|
|
7091
7102
|
{
|
|
7092
7103
|
name: "DATABASE_URL set",
|
|
7093
7104
|
check: () => {
|
|
7094
7105
|
try {
|
|
7095
|
-
const envContent =
|
|
7106
|
+
const envContent = fs23.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
|
|
7096
7107
|
const match = envContent.match(/^DATABASE_URL=(.+)$/m);
|
|
7097
7108
|
return match ? match[1] !== "" : false;
|
|
7098
7109
|
} catch {
|
|
@@ -7104,7 +7115,7 @@ async function doctorCommand(options) {
|
|
|
7104
7115
|
name: "JWT_SECRET set",
|
|
7105
7116
|
check: () => {
|
|
7106
7117
|
try {
|
|
7107
|
-
const envContent =
|
|
7118
|
+
const envContent = fs23.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
|
|
7108
7119
|
const match = envContent.match(/^JWT_SECRET=(.+)$/m);
|
|
7109
7120
|
if (!match || !match[1]) return false;
|
|
7110
7121
|
return match[1].length >= 32 ? true : "set but too short (need >=32 chars)";
|
|
@@ -7149,16 +7160,16 @@ async function doctorCommand(options) {
|
|
|
7149
7160
|
|
|
7150
7161
|
// src/commands/add.ts
|
|
7151
7162
|
init_logger();
|
|
7152
|
-
import
|
|
7163
|
+
import fs25 from "fs-extra";
|
|
7153
7164
|
import path25 from "path";
|
|
7154
7165
|
import pc3 from "picocolors";
|
|
7155
7166
|
|
|
7156
7167
|
// src/utils/doc-updater.ts
|
|
7157
|
-
import
|
|
7168
|
+
import fs24 from "fs-extra";
|
|
7158
7169
|
import path24 from "path";
|
|
7159
7170
|
async function appendToDbSchema(projectDir, modelName, fields) {
|
|
7160
7171
|
const filePath = path24.join(projectDir, "docs", "generated", "db-schema.md");
|
|
7161
|
-
if (!await
|
|
7172
|
+
if (!await fs24.pathExists(filePath)) return;
|
|
7162
7173
|
const entry = [
|
|
7163
7174
|
"",
|
|
7164
7175
|
`### ${modelName}`,
|
|
@@ -7168,12 +7179,12 @@ async function appendToDbSchema(projectDir, modelName, fields) {
|
|
|
7168
7179
|
...fields.map((f) => `| ${f} | |`),
|
|
7169
7180
|
""
|
|
7170
7181
|
].join("\n");
|
|
7171
|
-
await
|
|
7182
|
+
await fs24.appendFile(filePath, entry);
|
|
7172
7183
|
}
|
|
7173
7184
|
async function updateQualityScore(projectDir, domain, grade) {
|
|
7174
7185
|
const filePath = path24.join(projectDir, "docs", "QUALITY_SCORE.md");
|
|
7175
|
-
if (!await
|
|
7176
|
-
const content = await
|
|
7186
|
+
if (!await fs24.pathExists(filePath)) return;
|
|
7187
|
+
const content = await fs24.readFile(filePath, "utf-8");
|
|
7177
7188
|
const pattern = new RegExp(`^\\|\\s*${escapeRegex(domain)}\\s*\\|`, "m");
|
|
7178
7189
|
if (pattern.test(content)) {
|
|
7179
7190
|
const updated = content.replace(pattern, (match) => {
|
|
@@ -7181,11 +7192,11 @@ async function updateQualityScore(projectDir, domain, grade) {
|
|
|
7181
7192
|
parts[2] = ` ${grade} `;
|
|
7182
7193
|
return parts.join("|");
|
|
7183
7194
|
});
|
|
7184
|
-
await
|
|
7195
|
+
await fs24.writeFile(filePath, updated);
|
|
7185
7196
|
} else {
|
|
7186
7197
|
const row = `| ${domain} | ${grade} | |
|
|
7187
7198
|
`;
|
|
7188
|
-
await
|
|
7199
|
+
await fs24.appendFile(filePath, row);
|
|
7189
7200
|
}
|
|
7190
7201
|
}
|
|
7191
7202
|
function escapeRegex(str) {
|
|
@@ -7197,7 +7208,7 @@ init_rollback();
|
|
|
7197
7208
|
function ensureInProject() {
|
|
7198
7209
|
const cwd = process.cwd();
|
|
7199
7210
|
const configPath = path25.join(cwd, "src", "config", "app.config.ts");
|
|
7200
|
-
if (!
|
|
7211
|
+
if (!fs25.pathExistsSync(configPath)) {
|
|
7201
7212
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7202
7213
|
process.exit(1);
|
|
7203
7214
|
}
|
|
@@ -7231,7 +7242,7 @@ async function addFeatureCommand(name) {
|
|
|
7231
7242
|
const pascal = toPascal(name);
|
|
7232
7243
|
const camel = toCamel(name);
|
|
7233
7244
|
const featureDir = path25.join(root, "src", "features", kebab);
|
|
7234
|
-
if (await
|
|
7245
|
+
if (await fs25.pathExists(featureDir)) {
|
|
7235
7246
|
log.error(`Feature "${kebab}" already exists at src/features/${kebab}/`);
|
|
7236
7247
|
return;
|
|
7237
7248
|
}
|
|
@@ -7297,8 +7308,8 @@ export type Update${pascal}Input = z.infer<typeof ${camel}Schemas.update>;
|
|
|
7297
7308
|
let count = 0;
|
|
7298
7309
|
for (const [filePath, content] of Object.entries(files)) {
|
|
7299
7310
|
const fullPath = path25.join(featureDir, filePath);
|
|
7300
|
-
await
|
|
7301
|
-
await
|
|
7311
|
+
await fs25.ensureDir(path25.dirname(fullPath));
|
|
7312
|
+
await fs25.writeFile(fullPath, content);
|
|
7302
7313
|
count++;
|
|
7303
7314
|
}
|
|
7304
7315
|
await ctx.commit();
|
|
@@ -7320,7 +7331,7 @@ async function addPageCommand(routePath, options) {
|
|
|
7320
7331
|
const cleanPath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
|
|
7321
7332
|
const group = options.protected ? "(protected)" : "(public)";
|
|
7322
7333
|
const pageDir = path25.join(root, "src", "app", group, cleanPath);
|
|
7323
|
-
if (await
|
|
7334
|
+
if (await fs25.pathExists(path25.join(pageDir, "page.tsx"))) {
|
|
7324
7335
|
log.error(`Page already exists at src/app/${group}/${cleanPath}/page.tsx`);
|
|
7325
7336
|
return;
|
|
7326
7337
|
}
|
|
@@ -7368,9 +7379,9 @@ export default function Loading() {
|
|
|
7368
7379
|
const ctx = createRollbackContext();
|
|
7369
7380
|
try {
|
|
7370
7381
|
ctx.trackCreatedFile(pageDir);
|
|
7371
|
-
await
|
|
7372
|
-
await
|
|
7373
|
-
await
|
|
7382
|
+
await fs25.ensureDir(pageDir);
|
|
7383
|
+
await fs25.writeFile(path25.join(pageDir, "page.tsx"), pageContent);
|
|
7384
|
+
await fs25.writeFile(path25.join(pageDir, "loading.tsx"), loadingContent);
|
|
7374
7385
|
await ctx.commit();
|
|
7375
7386
|
log.success(`Created page at ${pc3.bold(`src/app/${group}/${cleanPath}/`)}`);
|
|
7376
7387
|
trackEvent("add", { type: "page" });
|
|
@@ -7389,7 +7400,7 @@ async function addModelCommand(name) {
|
|
|
7389
7400
|
const kebab = toKebab(name);
|
|
7390
7401
|
const schemaDir = path25.join(root, "prisma", "schema");
|
|
7391
7402
|
const schemaFile = path25.join(schemaDir, `${kebab}.prisma`);
|
|
7392
|
-
if (await
|
|
7403
|
+
if (await fs25.pathExists(schemaFile)) {
|
|
7393
7404
|
log.error(`Schema file already exists: prisma/schema/${kebab}.prisma`);
|
|
7394
7405
|
return;
|
|
7395
7406
|
}
|
|
@@ -7407,8 +7418,8 @@ async function addModelCommand(name) {
|
|
|
7407
7418
|
const ctx = createRollbackContext();
|
|
7408
7419
|
try {
|
|
7409
7420
|
ctx.trackCreatedFile(schemaFile);
|
|
7410
|
-
await
|
|
7411
|
-
await
|
|
7421
|
+
await fs25.ensureDir(schemaDir);
|
|
7422
|
+
await fs25.writeFile(schemaFile, content);
|
|
7412
7423
|
await ctx.commit();
|
|
7413
7424
|
log.success(`Created model ${pc3.bold(pascal)} at prisma/schema/${kebab}.prisma`);
|
|
7414
7425
|
trackEvent("add", { type: "model" });
|
|
@@ -7432,7 +7443,7 @@ async function addEmailCommand(name) {
|
|
|
7432
7443
|
const camel = toCamel(name);
|
|
7433
7444
|
const templatesDir = path25.join(root, "src", "lib", "core", "email", "templates");
|
|
7434
7445
|
const filePath = path25.join(templatesDir, `${kebab}-email.ts`);
|
|
7435
|
-
if (await
|
|
7446
|
+
if (await fs25.pathExists(filePath)) {
|
|
7436
7447
|
log.error(`Email template already exists: src/lib/core/email/templates/${kebab}-email.ts`);
|
|
7437
7448
|
return;
|
|
7438
7449
|
}
|
|
@@ -7479,22 +7490,22 @@ export function ${functionName}({ appName, actionUrl, userName }: ${pascal}Email
|
|
|
7479
7490
|
const indexPath = path25.join(templatesDir, "index.ts");
|
|
7480
7491
|
try {
|
|
7481
7492
|
ctx.trackCreatedFile(filePath);
|
|
7482
|
-
if (await
|
|
7493
|
+
if (await fs25.pathExists(indexPath)) {
|
|
7483
7494
|
await ctx.trackModifiedFile(indexPath);
|
|
7484
7495
|
} else {
|
|
7485
7496
|
ctx.trackCreatedFile(indexPath);
|
|
7486
7497
|
}
|
|
7487
|
-
await
|
|
7488
|
-
await
|
|
7498
|
+
await fs25.ensureDir(templatesDir);
|
|
7499
|
+
await fs25.writeFile(filePath, content);
|
|
7489
7500
|
const exportLine = `export { ${functionName} } from './${kebab}-email';
|
|
7490
7501
|
`;
|
|
7491
|
-
if (await
|
|
7492
|
-
const existing = await
|
|
7502
|
+
if (await fs25.pathExists(indexPath)) {
|
|
7503
|
+
const existing = await fs25.readFile(indexPath, "utf-8");
|
|
7493
7504
|
if (!existing.includes(functionName)) {
|
|
7494
|
-
await
|
|
7505
|
+
await fs25.appendFile(indexPath, exportLine);
|
|
7495
7506
|
}
|
|
7496
7507
|
} else {
|
|
7497
|
-
await
|
|
7508
|
+
await fs25.writeFile(indexPath, exportLine);
|
|
7498
7509
|
}
|
|
7499
7510
|
await ctx.commit();
|
|
7500
7511
|
log.success(`Created email template ${pc3.bold(kebab)} at src/lib/core/email/templates/${kebab}-email.ts`);
|
|
@@ -7518,9 +7529,9 @@ async function addComponentCommand(name, options) {
|
|
|
7518
7529
|
return;
|
|
7519
7530
|
}
|
|
7520
7531
|
const dir = type === "primitive" ? path25.join(root, "src", "components", "primitives") : path25.join(root, "src", "components", "patterns");
|
|
7521
|
-
await
|
|
7532
|
+
await fs25.ensureDir(dir);
|
|
7522
7533
|
const filePath = path25.join(dir, `${pascal}.tsx`);
|
|
7523
|
-
if (await
|
|
7534
|
+
if (await fs25.pathExists(filePath)) {
|
|
7524
7535
|
log.error(`Component already exists: ${pascal}.tsx`);
|
|
7525
7536
|
return;
|
|
7526
7537
|
}
|
|
@@ -7569,7 +7580,7 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7569
7580
|
const ctx = createRollbackContext();
|
|
7570
7581
|
try {
|
|
7571
7582
|
ctx.trackCreatedFile(filePath);
|
|
7572
|
-
await
|
|
7583
|
+
await fs25.writeFile(filePath, content);
|
|
7573
7584
|
await ctx.commit();
|
|
7574
7585
|
log.success(`Created ${type} component ${pc3.bold(pascal)} at ${path25.relative(root, filePath)}`);
|
|
7575
7586
|
trackEvent("add", { type: "component", componentType: type });
|
|
@@ -7586,14 +7597,14 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7586
7597
|
// src/commands/configure.ts
|
|
7587
7598
|
init_logger();
|
|
7588
7599
|
import { execSync as execSync2 } from "child_process";
|
|
7589
|
-
import
|
|
7600
|
+
import fs26 from "fs-extra";
|
|
7590
7601
|
import path26 from "path";
|
|
7591
7602
|
import pc4 from "picocolors";
|
|
7592
7603
|
import prompts6 from "prompts";
|
|
7593
7604
|
function ensureInProject2() {
|
|
7594
7605
|
const cwd = process.cwd();
|
|
7595
7606
|
const configPath = path26.join(cwd, "src", "config", "app.config.ts");
|
|
7596
|
-
if (!
|
|
7607
|
+
if (!fs26.pathExistsSync(configPath)) {
|
|
7597
7608
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7598
7609
|
process.exit(1);
|
|
7599
7610
|
}
|
|
@@ -7608,7 +7619,7 @@ var PROVIDER_DEPENDENCIES = {
|
|
|
7608
7619
|
};
|
|
7609
7620
|
function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
7610
7621
|
const configPath = path26.join(projectDir, "src", "config", "app.config.ts");
|
|
7611
|
-
let content =
|
|
7622
|
+
let content = fs26.readFileSync(configPath, "utf-8");
|
|
7612
7623
|
if (serviceKey === "auth") {
|
|
7613
7624
|
const boolValue = provider === "google" ? "true" : "false";
|
|
7614
7625
|
const featureRegex = new RegExp(`(googleOAuth\\s*:\\s*)(?:true|false)`);
|
|
@@ -7623,10 +7634,10 @@ function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
|
7623
7634
|
content = content.replace(featureRegex, `$1true`);
|
|
7624
7635
|
}
|
|
7625
7636
|
}
|
|
7626
|
-
|
|
7637
|
+
fs26.writeFileSync(configPath, content);
|
|
7627
7638
|
}
|
|
7628
7639
|
function detectPackageManager(projectDir) {
|
|
7629
|
-
if (
|
|
7640
|
+
if (fs26.existsSync(path26.join(projectDir, "yarn.lock"))) return "yarn";
|
|
7630
7641
|
return "npm";
|
|
7631
7642
|
}
|
|
7632
7643
|
function installDependencies(projectDir, deps) {
|
|
@@ -7737,14 +7748,14 @@ async function configureCommand(service) {
|
|
|
7737
7748
|
}
|
|
7738
7749
|
if (selectedProvider.envVars.length > 0) {
|
|
7739
7750
|
const envPath = path26.join(root, ".env");
|
|
7740
|
-
if (await
|
|
7741
|
-
const envContent = await
|
|
7751
|
+
if (await fs26.pathExists(envPath)) {
|
|
7752
|
+
const envContent = await fs26.readFile(envPath, "utf-8");
|
|
7742
7753
|
const missingVars = selectedProvider.envVars.filter(
|
|
7743
7754
|
(v) => !envContent.includes(v)
|
|
7744
7755
|
);
|
|
7745
7756
|
if (missingVars.length > 0) {
|
|
7746
7757
|
const additions = missingVars.map((v) => `${v}=""`).join("\n");
|
|
7747
|
-
await
|
|
7758
|
+
await fs26.appendFile(envPath, `
|
|
7748
7759
|
# ${selectedService} (${provider})
|
|
7749
7760
|
${additions}
|
|
7750
7761
|
`);
|
|
@@ -7773,7 +7784,7 @@ ${additions}
|
|
|
7773
7784
|
|
|
7774
7785
|
// src/commands/deploy.ts
|
|
7775
7786
|
init_logger();
|
|
7776
|
-
import
|
|
7787
|
+
import fs27 from "fs-extra";
|
|
7777
7788
|
import path27 from "path";
|
|
7778
7789
|
import { execSync as execSync3 } from "child_process";
|
|
7779
7790
|
import pc5 from "picocolors";
|
|
@@ -7781,7 +7792,7 @@ import prompts7 from "prompts";
|
|
|
7781
7792
|
function ensureInProject3() {
|
|
7782
7793
|
const cwd = process.cwd();
|
|
7783
7794
|
const configPath = path27.join(cwd, "src", "config", "app.config.ts");
|
|
7784
|
-
if (!
|
|
7795
|
+
if (!fs27.pathExistsSync(configPath)) {
|
|
7785
7796
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7786
7797
|
process.exit(1);
|
|
7787
7798
|
}
|
|
@@ -7821,7 +7832,7 @@ async function deployCommand() {
|
|
|
7821
7832
|
}
|
|
7822
7833
|
}
|
|
7823
7834
|
const vercelDir = path27.join(root, ".vercel");
|
|
7824
|
-
if (!
|
|
7835
|
+
if (!fs27.existsSync(vercelDir)) {
|
|
7825
7836
|
log.step("Linking project to Vercel...");
|
|
7826
7837
|
try {
|
|
7827
7838
|
execSync3("vercel link", { cwd: root, stdio: "inherit" });
|
|
@@ -7839,7 +7850,7 @@ async function deployCommand() {
|
|
|
7839
7850
|
const requiredVars = ["JWT_SECRET", "DATABASE_URL"];
|
|
7840
7851
|
log.step(`Core: ${pc5.dim(requiredVars.join(", "))}`);
|
|
7841
7852
|
const configPath = path27.join(root, "src", "config", "app.config.ts");
|
|
7842
|
-
const configContent = await
|
|
7853
|
+
const configContent = await fs27.readFile(configPath, "utf-8");
|
|
7843
7854
|
if (configContent.includes("email: { provider: 'sendgrid'")) {
|
|
7844
7855
|
log.step(`Email (SendGrid): ${pc5.dim("SENDGRID_API_KEY, SENDGRID_FROM_EMAIL")}`);
|
|
7845
7856
|
} else if (configContent.includes("email: { provider: 'resend'")) {
|
|
@@ -7900,7 +7911,7 @@ async function deployCommand() {
|
|
|
7900
7911
|
|
|
7901
7912
|
// src/commands/upgrade.ts
|
|
7902
7913
|
init_logger();
|
|
7903
|
-
import
|
|
7914
|
+
import fs28 from "fs-extra";
|
|
7904
7915
|
import path28 from "path";
|
|
7905
7916
|
import { execSync as execSync4 } from "child_process";
|
|
7906
7917
|
var MARS_PACKAGES = ["@mars-stack/core", "@mars-stack/ui"];
|
|
@@ -7924,7 +7935,7 @@ function readCurrentVersion(packageJson, packageName) {
|
|
|
7924
7935
|
return version ? version.replace(/^[\^~]/, "") : null;
|
|
7925
7936
|
}
|
|
7926
7937
|
function detectPackageManager2(projectDir) {
|
|
7927
|
-
if (
|
|
7938
|
+
if (fs28.pathExistsSync(path28.join(projectDir, "yarn.lock"))) {
|
|
7928
7939
|
return "yarn";
|
|
7929
7940
|
}
|
|
7930
7941
|
return "npm";
|
|
@@ -7988,7 +7999,7 @@ function updatePackageJsonVersions(packageJsonPath, packageJson, versions) {
|
|
|
7988
7999
|
if (updated.length > 0) {
|
|
7989
8000
|
packageJson.dependencies = deps;
|
|
7990
8001
|
packageJson.devDependencies = devDeps;
|
|
7991
|
-
|
|
8002
|
+
fs28.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
7992
8003
|
}
|
|
7993
8004
|
return updated;
|
|
7994
8005
|
}
|
|
@@ -7999,14 +8010,14 @@ function upgradeCommand(program2) {
|
|
|
7999
8010
|
log.title("MARS Upgrade");
|
|
8000
8011
|
const projectDir = process.cwd();
|
|
8001
8012
|
const packageJsonPath = path28.join(projectDir, "package.json");
|
|
8002
|
-
if (!
|
|
8013
|
+
if (!fs28.pathExistsSync(packageJsonPath)) {
|
|
8003
8014
|
log.error(
|
|
8004
8015
|
"No package.json found. Are you in a Mars project directory?"
|
|
8005
8016
|
);
|
|
8006
8017
|
process.exitCode = 1;
|
|
8007
8018
|
return;
|
|
8008
8019
|
}
|
|
8009
|
-
const packageJson =
|
|
8020
|
+
const packageJson = fs28.readJsonSync(packageJsonPath);
|
|
8010
8021
|
const hasMarsPackage = MARS_PACKAGES.some(
|
|
8011
8022
|
(name) => readCurrentVersion(packageJson, name) !== null
|
|
8012
8023
|
);
|