@mars-stack/cli 2.0.0 → 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 +475 -366
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/template/.cursor/skills/mars-address-pr-comments/SKILL.md +129 -0
- package/template/AGENTS.md +3 -2
- package/template/package.json +4 -4
- package/template/src/app/(auth)/forgotten-password/page.tsx +18 -0
- package/template/src/app/(auth)/register/page.tsx +3 -0
- package/template/src/app/(auth)/verify/page.tsx +19 -0
- package/template/src/app/(protected)/layout.tsx +18 -0
- package/template/src/app/api/auth/forgot/route.ts +7 -1
- package/template/src/app/api/auth/signup/route.ts +9 -0
- package/template/src/config/app.config.ts +0 -8
- package/template/src/config/routes.ts +2 -0
- /package/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,8 +44,8 @@ var init_logger = __esm({
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
// src/utils/rollback.ts
|
|
47
|
-
import
|
|
48
|
-
import
|
|
47
|
+
import fs4 from "fs-extra";
|
|
48
|
+
import path4 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 = path4.join(os.tmpdir(), `mars-rollback-${Date.now()}`);
|
|
57
57
|
function trackCreatedFile(filePath) {
|
|
58
58
|
manifest.filesCreated.push(filePath);
|
|
59
59
|
}
|
|
60
60
|
async function trackModifiedFile(filePath) {
|
|
61
|
-
if (!await
|
|
62
|
-
await
|
|
63
|
-
const backupPath =
|
|
64
|
-
await
|
|
61
|
+
if (!await fs4.pathExists(filePath)) return;
|
|
62
|
+
await fs4.ensureDir(backupDir);
|
|
63
|
+
const backupPath = path4.join(backupDir, `${manifest.filesModified.length}-${path4.basename(filePath)}`);
|
|
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
|
|
112
|
-
import
|
|
111
|
+
import fs5 from "fs-extra";
|
|
112
|
+
import path5 from "path";
|
|
113
113
|
async function addDependencies(projectRoot, deps, dev = false) {
|
|
114
|
-
const pkgPath =
|
|
115
|
-
if (!await
|
|
116
|
-
const pkg = await
|
|
114
|
+
const pkgPath = path5.join(projectRoot, "package.json");
|
|
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"() {
|
|
@@ -124,16 +124,40 @@ var init_dependencies = __esm({
|
|
|
124
124
|
}
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
// src/utils/routes.ts
|
|
128
|
+
import fs6 from "fs-extra";
|
|
129
|
+
import path6 from "path";
|
|
130
|
+
async function registerRoute(projectRoot, key, routePath, ctx) {
|
|
131
|
+
const routesFile = path6.join(projectRoot, "src", "config", "routes.ts");
|
|
132
|
+
if (!await fs6.pathExists(routesFile)) return;
|
|
133
|
+
let content = await fs6.readFile(routesFile, "utf-8");
|
|
134
|
+
if (content.includes(`${key}:`)) return;
|
|
135
|
+
if (ctx) {
|
|
136
|
+
await ctx.trackModifiedFile(routesFile);
|
|
137
|
+
}
|
|
138
|
+
content = content.replace(
|
|
139
|
+
/} as const;/,
|
|
140
|
+
` ${key}: '${routePath}',
|
|
141
|
+
} as const;`
|
|
142
|
+
);
|
|
143
|
+
await fs6.writeFile(routesFile, content);
|
|
144
|
+
}
|
|
145
|
+
var init_routes = __esm({
|
|
146
|
+
"src/utils/routes.ts"() {
|
|
147
|
+
"use strict";
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
127
151
|
// src/generators/features/blog.ts
|
|
128
152
|
var blog_exports = {};
|
|
129
153
|
__export(blog_exports, {
|
|
130
154
|
generateBlog: () => generateBlog
|
|
131
155
|
});
|
|
132
|
-
import
|
|
133
|
-
import
|
|
156
|
+
import fs7 from "fs-extra";
|
|
157
|
+
import path7 from "path";
|
|
134
158
|
async function generateBlog(projectRoot) {
|
|
135
|
-
const featureDir =
|
|
136
|
-
if (await
|
|
159
|
+
const featureDir = path7.join(projectRoot, "src", "features", "blog");
|
|
160
|
+
if (await fs7.pathExists(featureDir)) {
|
|
137
161
|
log.error("Blog feature already exists at src/features/blog/");
|
|
138
162
|
return;
|
|
139
163
|
}
|
|
@@ -154,10 +178,10 @@ async function generateBlog(projectRoot) {
|
|
|
154
178
|
};
|
|
155
179
|
let count = 0;
|
|
156
180
|
for (const [filePath, content] of Object.entries(files)) {
|
|
157
|
-
const fullPath =
|
|
181
|
+
const fullPath = path7.join(projectRoot, filePath);
|
|
158
182
|
ctx.trackCreatedFile(fullPath);
|
|
159
|
-
await
|
|
160
|
-
await
|
|
183
|
+
await fs7.ensureDir(path7.dirname(fullPath));
|
|
184
|
+
await fs7.writeFile(fullPath, content);
|
|
161
185
|
count++;
|
|
162
186
|
}
|
|
163
187
|
await addDependencies(projectRoot, {
|
|
@@ -168,6 +192,7 @@ async function generateBlog(projectRoot) {
|
|
|
168
192
|
"remark-gfm": "^4.0.0"
|
|
169
193
|
});
|
|
170
194
|
await addDependencies(projectRoot, { "@types/mdx": "^2.0.0" }, true);
|
|
195
|
+
await registerRoute(projectRoot, "blog", "/blog", ctx);
|
|
171
196
|
await setConfigFlag(projectRoot, ctx);
|
|
172
197
|
await ctx.commit();
|
|
173
198
|
log.success(`Generated blog feature with ${count} files`);
|
|
@@ -183,12 +208,12 @@ async function generateBlog(projectRoot) {
|
|
|
183
208
|
}
|
|
184
209
|
}
|
|
185
210
|
async function setConfigFlag(projectRoot, ctx) {
|
|
186
|
-
const configPath =
|
|
187
|
-
if (!await
|
|
211
|
+
const configPath = path7.join(projectRoot, "src", "config", "app.config.ts");
|
|
212
|
+
if (!await fs7.pathExists(configPath)) return;
|
|
188
213
|
await ctx.trackModifiedFile(configPath);
|
|
189
|
-
const content = await
|
|
214
|
+
const content = await fs7.readFile(configPath, "utf-8");
|
|
190
215
|
const updated = content.replace(/blog:\s*false/, "blog: true");
|
|
191
|
-
await
|
|
216
|
+
await fs7.writeFile(configPath, updated);
|
|
192
217
|
}
|
|
193
218
|
function schemas() {
|
|
194
219
|
return `${STAMP}
|
|
@@ -681,6 +706,7 @@ var init_blog = __esm({
|
|
|
681
706
|
init_logger();
|
|
682
707
|
init_rollback();
|
|
683
708
|
init_dependencies();
|
|
709
|
+
init_routes();
|
|
684
710
|
GENERATOR_VERSION = "0.1.0";
|
|
685
711
|
STAMP = `// @mars-generated blog@${GENERATOR_VERSION}`;
|
|
686
712
|
}
|
|
@@ -706,11 +732,11 @@ var dark_mode_exports = {};
|
|
|
706
732
|
__export(dark_mode_exports, {
|
|
707
733
|
generateDarkMode: () => generateDarkMode
|
|
708
734
|
});
|
|
709
|
-
import
|
|
710
|
-
import
|
|
735
|
+
import fs8 from "fs-extra";
|
|
736
|
+
import path8 from "path";
|
|
711
737
|
async function generateDarkMode(projectRoot) {
|
|
712
|
-
const featureDir =
|
|
713
|
-
if (await
|
|
738
|
+
const featureDir = path8.join(projectRoot, "src", "features", "dark-mode");
|
|
739
|
+
if (await fs8.pathExists(featureDir)) {
|
|
714
740
|
log.error("Dark mode feature already exists at src/features/dark-mode/");
|
|
715
741
|
return;
|
|
716
742
|
}
|
|
@@ -725,10 +751,10 @@ async function generateDarkMode(projectRoot) {
|
|
|
725
751
|
};
|
|
726
752
|
let count = 0;
|
|
727
753
|
for (const [filePath, content] of Object.entries(files)) {
|
|
728
|
-
const fullPath =
|
|
754
|
+
const fullPath = path8.join(projectRoot, filePath);
|
|
729
755
|
ctx.trackCreatedFile(fullPath);
|
|
730
|
-
await
|
|
731
|
-
await
|
|
756
|
+
await fs8.ensureDir(path8.dirname(fullPath));
|
|
757
|
+
await fs8.writeFile(fullPath, content);
|
|
732
758
|
count++;
|
|
733
759
|
}
|
|
734
760
|
await wireLayout(projectRoot, ctx);
|
|
@@ -758,18 +784,18 @@ async function generateDarkMode(projectRoot) {
|
|
|
758
784
|
}
|
|
759
785
|
}
|
|
760
786
|
async function setConfigFlag2(projectRoot, ctx) {
|
|
761
|
-
const configPath =
|
|
762
|
-
if (!await
|
|
787
|
+
const configPath = path8.join(projectRoot, "src", "config", "app.config.ts");
|
|
788
|
+
if (!await fs8.pathExists(configPath)) return;
|
|
763
789
|
await ctx.trackModifiedFile(configPath);
|
|
764
|
-
const content = await
|
|
790
|
+
const content = await fs8.readFile(configPath, "utf-8");
|
|
765
791
|
const updated = content.replace(/darkMode:\s*false/, "darkMode: true");
|
|
766
|
-
await
|
|
792
|
+
await fs8.writeFile(configPath, updated);
|
|
767
793
|
}
|
|
768
794
|
async function wireLayout(projectRoot, ctx) {
|
|
769
|
-
const layoutPath =
|
|
770
|
-
if (!await
|
|
795
|
+
const layoutPath = path8.join(projectRoot, "src", "app", "layout.tsx");
|
|
796
|
+
if (!await fs8.pathExists(layoutPath)) return;
|
|
771
797
|
await ctx.trackModifiedFile(layoutPath);
|
|
772
|
-
let content = await
|
|
798
|
+
let content = await fs8.readFile(layoutPath, "utf-8");
|
|
773
799
|
if (!content.includes("getThemeScript")) {
|
|
774
800
|
content = `import { getThemeScript } from '@/features/dark-mode';
|
|
775
801
|
${content}`;
|
|
@@ -783,13 +809,13 @@ ${content}`;
|
|
|
783
809
|
"$1\n <head>\n <script dangerouslySetInnerHTML={{ __html: getThemeScript() }} />\n </head>\n$2"
|
|
784
810
|
);
|
|
785
811
|
}
|
|
786
|
-
await
|
|
812
|
+
await fs8.writeFile(layoutPath, content);
|
|
787
813
|
}
|
|
788
814
|
async function wireProviders(projectRoot, ctx) {
|
|
789
|
-
const providersPath =
|
|
790
|
-
if (!await
|
|
815
|
+
const providersPath = path8.join(projectRoot, "src", "app", "providers.tsx");
|
|
816
|
+
if (!await fs8.pathExists(providersPath)) return;
|
|
791
817
|
await ctx.trackModifiedFile(providersPath);
|
|
792
|
-
let content = await
|
|
818
|
+
let content = await fs8.readFile(providersPath, "utf-8");
|
|
793
819
|
if (content.includes("ThemeProvider")) return;
|
|
794
820
|
content = insertImportAfterDirectives(
|
|
795
821
|
content,
|
|
@@ -808,13 +834,13 @@ async function wireProviders(projectRoot, ctx) {
|
|
|
808
834
|
);`;
|
|
809
835
|
}
|
|
810
836
|
);
|
|
811
|
-
await
|
|
837
|
+
await fs8.writeFile(providersPath, content);
|
|
812
838
|
}
|
|
813
839
|
async function wireProtectedNav(projectRoot, ctx) {
|
|
814
|
-
const navPath =
|
|
815
|
-
if (!await
|
|
840
|
+
const navPath = path8.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
841
|
+
if (!await fs8.pathExists(navPath)) return;
|
|
816
842
|
await ctx.trackModifiedFile(navPath);
|
|
817
|
-
let content = await
|
|
843
|
+
let content = await fs8.readFile(navPath, "utf-8");
|
|
818
844
|
if (content.includes("ThemeToggleSimple")) return;
|
|
819
845
|
content = insertImportAfterDirectives(
|
|
820
846
|
content,
|
|
@@ -829,19 +855,19 @@ async function wireProtectedNav(projectRoot, ctx) {
|
|
|
829
855
|
/(<div className="border-t border-border-default px-4 py-3">\s*\n\s*<UserMenu \/>)/,
|
|
830
856
|
'$1\n <div className="mt-2">\n <ThemeToggleSimple />\n </div>'
|
|
831
857
|
);
|
|
832
|
-
await
|
|
858
|
+
await fs8.writeFile(navPath, content);
|
|
833
859
|
}
|
|
834
860
|
async function wireTailwindDarkMode(projectRoot, ctx) {
|
|
835
|
-
const globalsPath =
|
|
836
|
-
if (!await
|
|
861
|
+
const globalsPath = path8.join(projectRoot, "src", "styles", "globals.css");
|
|
862
|
+
if (!await fs8.pathExists(globalsPath)) return;
|
|
837
863
|
await ctx.trackModifiedFile(globalsPath);
|
|
838
|
-
let content = await
|
|
864
|
+
let content = await fs8.readFile(globalsPath, "utf-8");
|
|
839
865
|
if (content.includes("@custom-variant dark")) return;
|
|
840
866
|
content = content.replace(
|
|
841
867
|
/@import 'tailwindcss';/,
|
|
842
868
|
"@import 'tailwindcss';\n@custom-variant dark (&:where(.dark, .dark *));"
|
|
843
869
|
);
|
|
844
|
-
await
|
|
870
|
+
await fs8.writeFile(globalsPath, content);
|
|
845
871
|
}
|
|
846
872
|
function themeProvider() {
|
|
847
873
|
return `${STAMP2}
|
|
@@ -1121,19 +1147,19 @@ var init_dark_mode = __esm({
|
|
|
1121
1147
|
});
|
|
1122
1148
|
|
|
1123
1149
|
// src/utils/prisma.ts
|
|
1124
|
-
import
|
|
1125
|
-
import
|
|
1150
|
+
import fs9 from "fs-extra";
|
|
1151
|
+
import path9 from "path";
|
|
1126
1152
|
async function addUserRelation(projectRoot, field, ctx) {
|
|
1127
|
-
const authPath =
|
|
1128
|
-
if (!await
|
|
1153
|
+
const authPath = path9.join(projectRoot, "prisma", "schema", "auth.prisma");
|
|
1154
|
+
if (!await fs9.pathExists(authPath)) return;
|
|
1129
1155
|
await ctx.trackModifiedFile(authPath);
|
|
1130
|
-
let content = await
|
|
1156
|
+
let content = await fs9.readFile(authPath, "utf-8");
|
|
1131
1157
|
if (content.includes(field)) return;
|
|
1132
1158
|
const insertBefore = " @@index([email])";
|
|
1133
1159
|
content = content.replace(insertBefore, ` ${field}
|
|
1134
1160
|
|
|
1135
1161
|
${insertBefore}`);
|
|
1136
|
-
await
|
|
1162
|
+
await fs9.writeFile(authPath, content);
|
|
1137
1163
|
}
|
|
1138
1164
|
var init_prisma = __esm({
|
|
1139
1165
|
"src/utils/prisma.ts"() {
|
|
@@ -1146,11 +1172,11 @@ var notifications_exports = {};
|
|
|
1146
1172
|
__export(notifications_exports, {
|
|
1147
1173
|
generateNotifications: () => generateNotifications
|
|
1148
1174
|
});
|
|
1149
|
-
import
|
|
1150
|
-
import
|
|
1175
|
+
import fs10 from "fs-extra";
|
|
1176
|
+
import path10 from "path";
|
|
1151
1177
|
async function generateNotifications(projectRoot) {
|
|
1152
|
-
const featureDir =
|
|
1153
|
-
if (await
|
|
1178
|
+
const featureDir = path10.join(projectRoot, "src", "features", "notifications");
|
|
1179
|
+
if (await fs10.pathExists(featureDir)) {
|
|
1154
1180
|
log.error("Notifications feature already exists at src/features/notifications/");
|
|
1155
1181
|
return;
|
|
1156
1182
|
}
|
|
@@ -1173,10 +1199,10 @@ async function generateNotifications(projectRoot) {
|
|
|
1173
1199
|
};
|
|
1174
1200
|
let count = 0;
|
|
1175
1201
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1176
|
-
const fullPath =
|
|
1202
|
+
const fullPath = path10.join(projectRoot, filePath);
|
|
1177
1203
|
ctx.trackCreatedFile(fullPath);
|
|
1178
|
-
await
|
|
1179
|
-
await
|
|
1204
|
+
await fs10.ensureDir(path10.dirname(fullPath));
|
|
1205
|
+
await fs10.writeFile(fullPath, content);
|
|
1180
1206
|
count++;
|
|
1181
1207
|
}
|
|
1182
1208
|
await addUserRelation(projectRoot, "notifications Notification[]", ctx);
|
|
@@ -1203,10 +1229,10 @@ async function generateNotifications(projectRoot) {
|
|
|
1203
1229
|
}
|
|
1204
1230
|
}
|
|
1205
1231
|
async function wireProtectedNav2(projectRoot, ctx) {
|
|
1206
|
-
const navPath =
|
|
1207
|
-
if (!await
|
|
1232
|
+
const navPath = path10.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
1233
|
+
if (!await fs10.pathExists(navPath)) return;
|
|
1208
1234
|
await ctx.trackModifiedFile(navPath);
|
|
1209
|
-
let content = await
|
|
1235
|
+
let content = await fs10.readFile(navPath, "utf-8");
|
|
1210
1236
|
if (content.includes("NotificationBell")) return;
|
|
1211
1237
|
content = insertImportAfterDirectives(
|
|
1212
1238
|
content,
|
|
@@ -1217,15 +1243,15 @@ async function wireProtectedNav2(projectRoot, ctx) {
|
|
|
1217
1243
|
/(<div className="flex items-center gap-3">)\s*\n(\s*)(<div className="hidden md:block">)/,
|
|
1218
1244
|
"$1\n$2<NotificationBell />\n$2$3"
|
|
1219
1245
|
);
|
|
1220
|
-
await
|
|
1246
|
+
await fs10.writeFile(navPath, content);
|
|
1221
1247
|
}
|
|
1222
1248
|
async function setConfigFlag3(projectRoot, ctx) {
|
|
1223
|
-
const configPath =
|
|
1224
|
-
if (!await
|
|
1249
|
+
const configPath = path10.join(projectRoot, "src", "config", "app.config.ts");
|
|
1250
|
+
if (!await fs10.pathExists(configPath)) return;
|
|
1225
1251
|
await ctx.trackModifiedFile(configPath);
|
|
1226
|
-
const content = await
|
|
1252
|
+
const content = await fs10.readFile(configPath, "utf-8");
|
|
1227
1253
|
const updated = content.replace(/notifications:\s*false/, "notifications: true");
|
|
1228
|
-
await
|
|
1254
|
+
await fs10.writeFile(configPath, updated);
|
|
1229
1255
|
}
|
|
1230
1256
|
function prismaSchema() {
|
|
1231
1257
|
return `// Generated by mars generate notifications (notifications@${GENERATOR_VERSION3})
|
|
@@ -1821,11 +1847,11 @@ var analytics_exports = {};
|
|
|
1821
1847
|
__export(analytics_exports, {
|
|
1822
1848
|
generateAnalytics: () => generateAnalytics
|
|
1823
1849
|
});
|
|
1824
|
-
import
|
|
1825
|
-
import
|
|
1850
|
+
import fs11 from "fs-extra";
|
|
1851
|
+
import path11 from "path";
|
|
1826
1852
|
async function generateAnalytics(projectRoot) {
|
|
1827
|
-
const featureDir =
|
|
1828
|
-
if (await
|
|
1853
|
+
const featureDir = path11.join(projectRoot, "src", "features", "analytics");
|
|
1854
|
+
if (await fs11.pathExists(featureDir)) {
|
|
1829
1855
|
log.error("Analytics feature already exists at src/features/analytics/");
|
|
1830
1856
|
return;
|
|
1831
1857
|
}
|
|
@@ -1840,10 +1866,10 @@ async function generateAnalytics(projectRoot) {
|
|
|
1840
1866
|
};
|
|
1841
1867
|
let count = 0;
|
|
1842
1868
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1843
|
-
const fullPath =
|
|
1869
|
+
const fullPath = path11.join(projectRoot, filePath);
|
|
1844
1870
|
ctx.trackCreatedFile(fullPath);
|
|
1845
|
-
await
|
|
1846
|
-
await
|
|
1871
|
+
await fs11.ensureDir(path11.dirname(fullPath));
|
|
1872
|
+
await fs11.writeFile(fullPath, content);
|
|
1847
1873
|
count++;
|
|
1848
1874
|
}
|
|
1849
1875
|
await wireProviders2(projectRoot, ctx);
|
|
@@ -1874,10 +1900,10 @@ async function generateAnalytics(projectRoot) {
|
|
|
1874
1900
|
}
|
|
1875
1901
|
}
|
|
1876
1902
|
async function wireProviders2(projectRoot, ctx) {
|
|
1877
|
-
const providersPath =
|
|
1878
|
-
if (!await
|
|
1903
|
+
const providersPath = path11.join(projectRoot, "src", "app", "providers.tsx");
|
|
1904
|
+
if (!await fs11.pathExists(providersPath)) return;
|
|
1879
1905
|
await ctx.trackModifiedFile(providersPath);
|
|
1880
|
-
let content = await
|
|
1906
|
+
let content = await fs11.readFile(providersPath, "utf-8");
|
|
1881
1907
|
if (content.includes("AnalyticsProvider")) return;
|
|
1882
1908
|
content = insertImportAfterDirectives(
|
|
1883
1909
|
content,
|
|
@@ -1892,8 +1918,8 @@ async function wireProviders2(projectRoot, ctx) {
|
|
|
1892
1918
|
);`;
|
|
1893
1919
|
}
|
|
1894
1920
|
);
|
|
1895
|
-
await
|
|
1896
|
-
const written = await
|
|
1921
|
+
await fs11.writeFile(providersPath, content);
|
|
1922
|
+
const written = await fs11.readFile(providersPath, "utf-8");
|
|
1897
1923
|
if (!written.includes("AnalyticsProvider")) {
|
|
1898
1924
|
throw new Error(
|
|
1899
1925
|
"wireProviders: AnalyticsProvider was not inserted into providers.tsx \u2014 the return statement pattern did not match. Review the template file structure."
|
|
@@ -1901,12 +1927,12 @@ async function wireProviders2(projectRoot, ctx) {
|
|
|
1901
1927
|
}
|
|
1902
1928
|
}
|
|
1903
1929
|
async function setConfigFlag4(projectRoot, ctx) {
|
|
1904
|
-
const configPath =
|
|
1905
|
-
if (!await
|
|
1930
|
+
const configPath = path11.join(projectRoot, "src", "config", "app.config.ts");
|
|
1931
|
+
if (!await fs11.pathExists(configPath)) return;
|
|
1906
1932
|
await ctx.trackModifiedFile(configPath);
|
|
1907
|
-
const content = await
|
|
1933
|
+
const content = await fs11.readFile(configPath, "utf-8");
|
|
1908
1934
|
const updated = content.replace(/analytics:\s*false/, "analytics: true");
|
|
1909
|
-
await
|
|
1935
|
+
await fs11.writeFile(configPath, updated);
|
|
1910
1936
|
}
|
|
1911
1937
|
function types3() {
|
|
1912
1938
|
return `${STAMP4}
|
|
@@ -2265,11 +2291,11 @@ var command_palette_exports = {};
|
|
|
2265
2291
|
__export(command_palette_exports, {
|
|
2266
2292
|
generateCommandPalette: () => generateCommandPalette
|
|
2267
2293
|
});
|
|
2268
|
-
import
|
|
2269
|
-
import
|
|
2294
|
+
import fs12 from "fs-extra";
|
|
2295
|
+
import path12 from "path";
|
|
2270
2296
|
async function generateCommandPalette(projectRoot) {
|
|
2271
|
-
const featureDir =
|
|
2272
|
-
if (await
|
|
2297
|
+
const featureDir = path12.join(projectRoot, "src", "features", "command-palette");
|
|
2298
|
+
if (await fs12.pathExists(featureDir)) {
|
|
2273
2299
|
log.error("Command palette feature already exists at src/features/command-palette/");
|
|
2274
2300
|
return;
|
|
2275
2301
|
}
|
|
@@ -2287,10 +2313,10 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2287
2313
|
};
|
|
2288
2314
|
let count = 0;
|
|
2289
2315
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2290
|
-
const fullPath =
|
|
2316
|
+
const fullPath = path12.join(projectRoot, filePath);
|
|
2291
2317
|
ctx.trackCreatedFile(fullPath);
|
|
2292
|
-
await
|
|
2293
|
-
await
|
|
2318
|
+
await fs12.ensureDir(path12.dirname(fullPath));
|
|
2319
|
+
await fs12.writeFile(fullPath, content);
|
|
2294
2320
|
count++;
|
|
2295
2321
|
}
|
|
2296
2322
|
await addDependencies(projectRoot, { cmdk: "^1.0.0" });
|
|
@@ -2313,26 +2339,26 @@ async function generateCommandPalette(projectRoot) {
|
|
|
2313
2339
|
}
|
|
2314
2340
|
}
|
|
2315
2341
|
async function wireLayout2(projectRoot, ctx) {
|
|
2316
|
-
const layoutPath =
|
|
2317
|
-
if (!await
|
|
2318
|
-
await
|
|
2319
|
-
let content = await fs10.readFile(layoutPath, "utf-8");
|
|
2342
|
+
const layoutPath = path12.join(projectRoot, "src", "app", "layout.tsx");
|
|
2343
|
+
if (!await fs12.pathExists(layoutPath)) return;
|
|
2344
|
+
let content = await fs12.readFile(layoutPath, "utf-8");
|
|
2320
2345
|
if (content.includes("CommandPalette")) return;
|
|
2346
|
+
await ctx.trackModifiedFile(layoutPath);
|
|
2321
2347
|
content = `import { CommandPalette } from '@/features/command-palette';
|
|
2322
2348
|
${content}`;
|
|
2323
2349
|
content = content.replace(
|
|
2324
|
-
/(\s*)(<Providers
|
|
2350
|
+
/(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
|
|
2325
2351
|
"$1$2\n$1<CommandPalette />"
|
|
2326
2352
|
);
|
|
2327
|
-
await
|
|
2353
|
+
await fs12.writeFile(layoutPath, content);
|
|
2328
2354
|
}
|
|
2329
2355
|
async function setConfigFlag5(projectRoot, ctx) {
|
|
2330
|
-
const configPath =
|
|
2331
|
-
if (!await
|
|
2356
|
+
const configPath = path12.join(projectRoot, "src", "config", "app.config.ts");
|
|
2357
|
+
if (!await fs12.pathExists(configPath)) return;
|
|
2332
2358
|
await ctx.trackModifiedFile(configPath);
|
|
2333
|
-
const content = await
|
|
2359
|
+
const content = await fs12.readFile(configPath, "utf-8");
|
|
2334
2360
|
const updated = content.replace(/commandPalette:\s*false/, "commandPalette: true");
|
|
2335
|
-
await
|
|
2361
|
+
await fs12.writeFile(configPath, updated);
|
|
2336
2362
|
}
|
|
2337
2363
|
function types4() {
|
|
2338
2364
|
return `${STAMP5}
|
|
@@ -2748,11 +2774,11 @@ var onboarding_exports = {};
|
|
|
2748
2774
|
__export(onboarding_exports, {
|
|
2749
2775
|
generateOnboarding: () => generateOnboarding
|
|
2750
2776
|
});
|
|
2751
|
-
import
|
|
2752
|
-
import
|
|
2777
|
+
import fs13 from "fs-extra";
|
|
2778
|
+
import path13 from "path";
|
|
2753
2779
|
async function generateOnboarding(projectRoot) {
|
|
2754
|
-
const featureDir =
|
|
2755
|
-
if (await
|
|
2780
|
+
const featureDir = path13.join(projectRoot, "src", "features", "onboarding");
|
|
2781
|
+
if (await fs13.pathExists(featureDir)) {
|
|
2756
2782
|
log.error("Onboarding feature already exists at src/features/onboarding/");
|
|
2757
2783
|
return;
|
|
2758
2784
|
}
|
|
@@ -2774,13 +2800,14 @@ async function generateOnboarding(projectRoot) {
|
|
|
2774
2800
|
};
|
|
2775
2801
|
let count = 0;
|
|
2776
2802
|
for (const [filePath, content] of Object.entries(files)) {
|
|
2777
|
-
const fullPath =
|
|
2803
|
+
const fullPath = path13.join(projectRoot, filePath);
|
|
2778
2804
|
ctx.trackCreatedFile(fullPath);
|
|
2779
|
-
await
|
|
2780
|
-
await
|
|
2805
|
+
await fs13.ensureDir(path13.dirname(fullPath));
|
|
2806
|
+
await fs13.writeFile(fullPath, content);
|
|
2781
2807
|
count++;
|
|
2782
2808
|
}
|
|
2783
2809
|
await addUserRelation(projectRoot, "onboardingProgress OnboardingProgress?", ctx);
|
|
2810
|
+
await registerRoute(projectRoot, "onboarding", "/onboarding", ctx);
|
|
2784
2811
|
await setConfigFlag6(projectRoot, ctx);
|
|
2785
2812
|
await ctx.commit();
|
|
2786
2813
|
log.success(`Generated onboarding feature with ${count} files`);
|
|
@@ -2801,12 +2828,12 @@ async function generateOnboarding(projectRoot) {
|
|
|
2801
2828
|
}
|
|
2802
2829
|
}
|
|
2803
2830
|
async function setConfigFlag6(projectRoot, ctx) {
|
|
2804
|
-
const configPath =
|
|
2805
|
-
if (!await
|
|
2831
|
+
const configPath = path13.join(projectRoot, "src", "config", "app.config.ts");
|
|
2832
|
+
if (!await fs13.pathExists(configPath)) return;
|
|
2806
2833
|
await ctx.trackModifiedFile(configPath);
|
|
2807
|
-
const content = await
|
|
2834
|
+
const content = await fs13.readFile(configPath, "utf-8");
|
|
2808
2835
|
const updated = content.replace(/onboarding:\s*false/, "onboarding: true");
|
|
2809
|
-
await
|
|
2836
|
+
await fs13.writeFile(configPath, updated);
|
|
2810
2837
|
}
|
|
2811
2838
|
function prismaSchema2() {
|
|
2812
2839
|
return `// Generated by mars generate onboarding (onboarding@${GENERATOR_VERSION6})
|
|
@@ -3322,6 +3349,7 @@ var init_onboarding = __esm({
|
|
|
3322
3349
|
init_logger();
|
|
3323
3350
|
init_rollback();
|
|
3324
3351
|
init_prisma();
|
|
3352
|
+
init_routes();
|
|
3325
3353
|
GENERATOR_VERSION6 = "0.1.0";
|
|
3326
3354
|
STAMP6 = `// @mars-generated onboarding@${GENERATOR_VERSION6}`;
|
|
3327
3355
|
}
|
|
@@ -3332,11 +3360,11 @@ var search_exports = {};
|
|
|
3332
3360
|
__export(search_exports, {
|
|
3333
3361
|
generateSearch: () => generateSearch
|
|
3334
3362
|
});
|
|
3335
|
-
import
|
|
3336
|
-
import
|
|
3363
|
+
import fs14 from "fs-extra";
|
|
3364
|
+
import path14 from "path";
|
|
3337
3365
|
async function generateSearch(projectRoot) {
|
|
3338
|
-
const featureDir =
|
|
3339
|
-
if (await
|
|
3366
|
+
const featureDir = path14.join(projectRoot, "src", "features", "search");
|
|
3367
|
+
if (await fs14.pathExists(featureDir)) {
|
|
3340
3368
|
log.error("Search feature already exists at src/features/search/");
|
|
3341
3369
|
return;
|
|
3342
3370
|
}
|
|
@@ -3349,25 +3377,30 @@ async function generateSearch(projectRoot) {
|
|
|
3349
3377
|
"src/features/search/server/index.ts": serverIndex(),
|
|
3350
3378
|
"src/features/search/hooks/use-search.ts": useSearchHook(),
|
|
3351
3379
|
"src/features/search/components/SearchInput.tsx": searchInput(),
|
|
3380
|
+
"src/features/search/components/NavSearch.tsx": navSearch(),
|
|
3352
3381
|
"src/features/search/components/index.ts": componentIndex5(),
|
|
3353
3382
|
"src/features/search/index.ts": featureIndex4(),
|
|
3354
3383
|
"src/app/api/protected/search/route.ts": searchRoute()
|
|
3355
3384
|
};
|
|
3356
3385
|
let count = 0;
|
|
3357
3386
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3358
|
-
const fullPath =
|
|
3387
|
+
const fullPath = path14.join(projectRoot, filePath);
|
|
3359
3388
|
ctx.trackCreatedFile(fullPath);
|
|
3360
|
-
await
|
|
3361
|
-
await
|
|
3389
|
+
await fs14.ensureDir(path14.dirname(fullPath));
|
|
3390
|
+
await fs14.writeFile(fullPath, content);
|
|
3362
3391
|
count++;
|
|
3363
3392
|
}
|
|
3393
|
+
await wireProtectedNav3(projectRoot, ctx);
|
|
3364
3394
|
await setConfigFlag7(projectRoot, ctx);
|
|
3365
3395
|
await ctx.commit();
|
|
3366
|
-
log.success(`Generated search feature
|
|
3396
|
+
log.success(`Generated and wired search feature (${count} files created)`);
|
|
3367
3397
|
log.blank();
|
|
3368
3398
|
log.step("src/features/search/ \u2014 types, validation, server logic, hooks, components");
|
|
3369
3399
|
log.step("src/app/api/protected/search/ \u2014 authenticated search endpoint");
|
|
3370
3400
|
log.blank();
|
|
3401
|
+
log.step("Wired automatically:");
|
|
3402
|
+
log.step(" \u2713 NavSearch added to navigation bar");
|
|
3403
|
+
log.blank();
|
|
3371
3404
|
log.warn("Search uses Postgres full-text search by default (no extra setup).");
|
|
3372
3405
|
log.blank();
|
|
3373
3406
|
log.step("For Algolia: yarn add algoliasearch + set ALGOLIA_APP_ID, ALGOLIA_API_KEY env vars");
|
|
@@ -3381,13 +3414,30 @@ async function generateSearch(projectRoot) {
|
|
|
3381
3414
|
throw error;
|
|
3382
3415
|
}
|
|
3383
3416
|
}
|
|
3417
|
+
async function wireProtectedNav3(projectRoot, ctx) {
|
|
3418
|
+
const navPath = path14.join(projectRoot, "src", "app", "(protected)", "layout.tsx");
|
|
3419
|
+
if (!await fs14.pathExists(navPath)) return;
|
|
3420
|
+
let content = await fs14.readFile(navPath, "utf-8");
|
|
3421
|
+
if (content.includes("NavSearch")) return;
|
|
3422
|
+
await ctx.trackModifiedFile(navPath);
|
|
3423
|
+
content = insertImportAfterDirectives(
|
|
3424
|
+
content,
|
|
3425
|
+
`import { NavSearch } from '@/features/search';
|
|
3426
|
+
`
|
|
3427
|
+
);
|
|
3428
|
+
content = content.replace(
|
|
3429
|
+
/(<div className="flex items-center gap-3">)\s*\n(\s*)(<)/,
|
|
3430
|
+
"$1\n$2<NavSearch />\n$2$3"
|
|
3431
|
+
);
|
|
3432
|
+
await fs14.writeFile(navPath, content);
|
|
3433
|
+
}
|
|
3384
3434
|
async function setConfigFlag7(projectRoot, ctx) {
|
|
3385
|
-
const configPath =
|
|
3386
|
-
if (!await
|
|
3435
|
+
const configPath = path14.join(projectRoot, "src", "config", "app.config.ts");
|
|
3436
|
+
if (!await fs14.pathExists(configPath)) return;
|
|
3387
3437
|
await ctx.trackModifiedFile(configPath);
|
|
3388
|
-
const content = await
|
|
3438
|
+
const content = await fs14.readFile(configPath, "utf-8");
|
|
3389
3439
|
const updated = content.replace(/search:\s*false/, "search: true");
|
|
3390
|
-
await
|
|
3440
|
+
await fs14.writeFile(configPath, updated);
|
|
3391
3441
|
}
|
|
3392
3442
|
function types6() {
|
|
3393
3443
|
return `${STAMP7}
|
|
@@ -3657,9 +3707,31 @@ export function SearchInput({
|
|
|
3657
3707
|
}
|
|
3658
3708
|
`;
|
|
3659
3709
|
}
|
|
3710
|
+
function navSearch() {
|
|
3711
|
+
return `${STAMP7}
|
|
3712
|
+
'use client';
|
|
3713
|
+
|
|
3714
|
+
import { useState } from 'react';
|
|
3715
|
+
import { SearchInput } from './SearchInput';
|
|
3716
|
+
|
|
3717
|
+
export function NavSearch() {
|
|
3718
|
+
const [query, setQuery] = useState('');
|
|
3719
|
+
|
|
3720
|
+
return (
|
|
3721
|
+
<SearchInput
|
|
3722
|
+
value={query}
|
|
3723
|
+
onQueryChange={setQuery}
|
|
3724
|
+
placeholder="Search\u2026"
|
|
3725
|
+
className="w-48 lg:w-64"
|
|
3726
|
+
/>
|
|
3727
|
+
);
|
|
3728
|
+
}
|
|
3729
|
+
`;
|
|
3730
|
+
}
|
|
3660
3731
|
function componentIndex5() {
|
|
3661
3732
|
return `${STAMP7}
|
|
3662
3733
|
export { SearchInput } from './SearchInput';
|
|
3734
|
+
export { NavSearch } from './NavSearch';
|
|
3663
3735
|
`;
|
|
3664
3736
|
}
|
|
3665
3737
|
function featureIndex4() {
|
|
@@ -3667,7 +3739,7 @@ function featureIndex4() {
|
|
|
3667
3739
|
export type { SearchResult, SearchOptions, SearchProvider } from './types';
|
|
3668
3740
|
export { searchParamsSchema } from './validation/schemas';
|
|
3669
3741
|
export type { SearchParams } from './validation/schemas';
|
|
3670
|
-
export { SearchInput } from './components';
|
|
3742
|
+
export { SearchInput, NavSearch } from './components';
|
|
3671
3743
|
export { useSearch } from './hooks/use-search';
|
|
3672
3744
|
`;
|
|
3673
3745
|
}
|
|
@@ -3706,6 +3778,7 @@ var GENERATOR_VERSION7, STAMP7;
|
|
|
3706
3778
|
var init_search = __esm({
|
|
3707
3779
|
"src/generators/features/search.ts"() {
|
|
3708
3780
|
"use strict";
|
|
3781
|
+
init_client_file_patch();
|
|
3709
3782
|
init_logger();
|
|
3710
3783
|
init_rollback();
|
|
3711
3784
|
GENERATOR_VERSION7 = "0.1.0";
|
|
@@ -3718,11 +3791,11 @@ var realtime_exports = {};
|
|
|
3718
3791
|
__export(realtime_exports, {
|
|
3719
3792
|
generateRealtime: () => generateRealtime
|
|
3720
3793
|
});
|
|
3721
|
-
import
|
|
3722
|
-
import
|
|
3794
|
+
import fs15 from "fs-extra";
|
|
3795
|
+
import path15 from "path";
|
|
3723
3796
|
async function generateRealtime(projectRoot) {
|
|
3724
|
-
const featureDir =
|
|
3725
|
-
if (await
|
|
3797
|
+
const featureDir = path15.join(projectRoot, "src", "features", "realtime");
|
|
3798
|
+
if (await fs15.pathExists(featureDir)) {
|
|
3726
3799
|
log.error("Realtime feature already exists at src/features/realtime/");
|
|
3727
3800
|
return;
|
|
3728
3801
|
}
|
|
@@ -3739,10 +3812,10 @@ async function generateRealtime(projectRoot) {
|
|
|
3739
3812
|
};
|
|
3740
3813
|
let count = 0;
|
|
3741
3814
|
for (const [filePath, content] of Object.entries(files)) {
|
|
3742
|
-
const fullPath =
|
|
3815
|
+
const fullPath = path15.join(projectRoot, filePath);
|
|
3743
3816
|
ctx.trackCreatedFile(fullPath);
|
|
3744
|
-
await
|
|
3745
|
-
await
|
|
3817
|
+
await fs15.ensureDir(path15.dirname(fullPath));
|
|
3818
|
+
await fs15.writeFile(fullPath, content);
|
|
3746
3819
|
count++;
|
|
3747
3820
|
}
|
|
3748
3821
|
await setConfigFlag8(projectRoot, ctx);
|
|
@@ -3767,12 +3840,12 @@ async function generateRealtime(projectRoot) {
|
|
|
3767
3840
|
}
|
|
3768
3841
|
}
|
|
3769
3842
|
async function setConfigFlag8(projectRoot, ctx) {
|
|
3770
|
-
const configPath =
|
|
3771
|
-
if (!await
|
|
3843
|
+
const configPath = path15.join(projectRoot, "src", "config", "app.config.ts");
|
|
3844
|
+
if (!await fs15.pathExists(configPath)) return;
|
|
3772
3845
|
await ctx.trackModifiedFile(configPath);
|
|
3773
|
-
const content = await
|
|
3846
|
+
const content = await fs15.readFile(configPath, "utf-8");
|
|
3774
3847
|
const updated = content.replace(/realtime:\s*false/, "realtime: true");
|
|
3775
|
-
await
|
|
3848
|
+
await fs15.writeFile(configPath, updated);
|
|
3776
3849
|
}
|
|
3777
3850
|
function types7() {
|
|
3778
3851
|
return `${STAMP8}
|
|
@@ -4081,11 +4154,11 @@ var ai_exports = {};
|
|
|
4081
4154
|
__export(ai_exports, {
|
|
4082
4155
|
generateAI: () => generateAI
|
|
4083
4156
|
});
|
|
4084
|
-
import
|
|
4085
|
-
import
|
|
4157
|
+
import fs16 from "fs-extra";
|
|
4158
|
+
import path16 from "path";
|
|
4086
4159
|
async function generateAI(projectRoot) {
|
|
4087
|
-
const featureDir =
|
|
4088
|
-
if (await
|
|
4160
|
+
const featureDir = path16.join(projectRoot, "src", "features", "ai");
|
|
4161
|
+
if (await fs16.pathExists(featureDir)) {
|
|
4089
4162
|
log.error("AI feature already exists at src/features/ai/");
|
|
4090
4163
|
return;
|
|
4091
4164
|
}
|
|
@@ -4103,10 +4176,10 @@ async function generateAI(projectRoot) {
|
|
|
4103
4176
|
};
|
|
4104
4177
|
let count = 0;
|
|
4105
4178
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4106
|
-
const fullPath =
|
|
4179
|
+
const fullPath = path16.join(projectRoot, filePath);
|
|
4107
4180
|
ctx.trackCreatedFile(fullPath);
|
|
4108
|
-
await
|
|
4109
|
-
await
|
|
4181
|
+
await fs16.ensureDir(path16.dirname(fullPath));
|
|
4182
|
+
await fs16.writeFile(fullPath, content);
|
|
4110
4183
|
count++;
|
|
4111
4184
|
}
|
|
4112
4185
|
await addDependencies(projectRoot, {
|
|
@@ -4132,12 +4205,12 @@ async function generateAI(projectRoot) {
|
|
|
4132
4205
|
}
|
|
4133
4206
|
}
|
|
4134
4207
|
async function setConfigFlag9(projectRoot, ctx) {
|
|
4135
|
-
const configPath =
|
|
4136
|
-
if (!await
|
|
4208
|
+
const configPath = path16.join(projectRoot, "src", "config", "app.config.ts");
|
|
4209
|
+
if (!await fs16.pathExists(configPath)) return;
|
|
4137
4210
|
await ctx.trackModifiedFile(configPath);
|
|
4138
|
-
const content = await
|
|
4211
|
+
const content = await fs16.readFile(configPath, "utf-8");
|
|
4139
4212
|
const updated = content.replace(/ai:\s*false/, "ai: true");
|
|
4140
|
-
await
|
|
4213
|
+
await fs16.writeFile(configPath, updated);
|
|
4141
4214
|
}
|
|
4142
4215
|
function types8() {
|
|
4143
4216
|
return `${STAMP9}
|
|
@@ -4576,11 +4649,11 @@ var cookie_consent_exports = {};
|
|
|
4576
4649
|
__export(cookie_consent_exports, {
|
|
4577
4650
|
generateCookieConsent: () => generateCookieConsent
|
|
4578
4651
|
});
|
|
4579
|
-
import
|
|
4580
|
-
import
|
|
4652
|
+
import fs17 from "fs-extra";
|
|
4653
|
+
import path17 from "path";
|
|
4581
4654
|
async function generateCookieConsent(projectRoot) {
|
|
4582
|
-
const featureDir =
|
|
4583
|
-
if (await
|
|
4655
|
+
const featureDir = path17.join(projectRoot, "src", "features", "cookie-consent");
|
|
4656
|
+
if (await fs17.pathExists(featureDir)) {
|
|
4584
4657
|
log.error("Cookie consent feature already exists at src/features/cookie-consent/");
|
|
4585
4658
|
return;
|
|
4586
4659
|
}
|
|
@@ -4596,10 +4669,10 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4596
4669
|
};
|
|
4597
4670
|
let count = 0;
|
|
4598
4671
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4599
|
-
const fullPath =
|
|
4672
|
+
const fullPath = path17.join(projectRoot, filePath);
|
|
4600
4673
|
ctx.trackCreatedFile(fullPath);
|
|
4601
|
-
await
|
|
4602
|
-
await
|
|
4674
|
+
await fs17.ensureDir(path17.dirname(fullPath));
|
|
4675
|
+
await fs17.writeFile(fullPath, content);
|
|
4603
4676
|
count++;
|
|
4604
4677
|
}
|
|
4605
4678
|
await wireLayout3(projectRoot, ctx);
|
|
@@ -4624,26 +4697,26 @@ async function generateCookieConsent(projectRoot) {
|
|
|
4624
4697
|
}
|
|
4625
4698
|
}
|
|
4626
4699
|
async function wireLayout3(projectRoot, ctx) {
|
|
4627
|
-
const layoutPath =
|
|
4628
|
-
if (!await
|
|
4629
|
-
await
|
|
4630
|
-
let content = await fs15.readFile(layoutPath, "utf-8");
|
|
4700
|
+
const layoutPath = path17.join(projectRoot, "src", "app", "layout.tsx");
|
|
4701
|
+
if (!await fs17.pathExists(layoutPath)) return;
|
|
4702
|
+
let content = await fs17.readFile(layoutPath, "utf-8");
|
|
4631
4703
|
if (content.includes("CookieConsentBanner")) return;
|
|
4704
|
+
await ctx.trackModifiedFile(layoutPath);
|
|
4632
4705
|
content = `import { CookieConsentBanner } from '@/features/cookie-consent';
|
|
4633
4706
|
${content}`;
|
|
4634
4707
|
content = content.replace(
|
|
4635
|
-
/(\s*)(<Providers
|
|
4708
|
+
/(\s*)(<Providers>[\s\S]*?<\/Providers>)/,
|
|
4636
4709
|
"$1$2\n$1<CookieConsentBanner />"
|
|
4637
4710
|
);
|
|
4638
|
-
await
|
|
4711
|
+
await fs17.writeFile(layoutPath, content);
|
|
4639
4712
|
}
|
|
4640
4713
|
async function setConfigFlag10(projectRoot, ctx) {
|
|
4641
|
-
const configPath =
|
|
4642
|
-
if (!await
|
|
4714
|
+
const configPath = path17.join(projectRoot, "src", "config", "app.config.ts");
|
|
4715
|
+
if (!await fs17.pathExists(configPath)) return;
|
|
4643
4716
|
await ctx.trackModifiedFile(configPath);
|
|
4644
|
-
const content = await
|
|
4717
|
+
const content = await fs17.readFile(configPath, "utf-8");
|
|
4645
4718
|
const updated = content.replace(/cookieConsent:\s*false/, "cookieConsent: true");
|
|
4646
|
-
await
|
|
4719
|
+
await fs17.writeFile(configPath, updated);
|
|
4647
4720
|
}
|
|
4648
4721
|
function types9() {
|
|
4649
4722
|
return `${STAMP10}
|
|
@@ -4928,11 +5001,11 @@ var coming_soon_exports = {};
|
|
|
4928
5001
|
__export(coming_soon_exports, {
|
|
4929
5002
|
generateComingSoon: () => generateComingSoon
|
|
4930
5003
|
});
|
|
4931
|
-
import
|
|
4932
|
-
import
|
|
5004
|
+
import fs18 from "fs-extra";
|
|
5005
|
+
import path18 from "path";
|
|
4933
5006
|
async function generateComingSoon(projectRoot) {
|
|
4934
|
-
const featureDir =
|
|
4935
|
-
if (await
|
|
5007
|
+
const featureDir = path18.join(projectRoot, "src", "features", "coming-soon");
|
|
5008
|
+
if (await fs18.pathExists(featureDir)) {
|
|
4936
5009
|
log.error("Coming soon feature already exists at src/features/coming-soon/");
|
|
4937
5010
|
return;
|
|
4938
5011
|
}
|
|
@@ -4949,12 +5022,13 @@ async function generateComingSoon(projectRoot) {
|
|
|
4949
5022
|
};
|
|
4950
5023
|
let count = 0;
|
|
4951
5024
|
for (const [filePath, content] of Object.entries(files)) {
|
|
4952
|
-
const fullPath =
|
|
5025
|
+
const fullPath = path18.join(projectRoot, filePath);
|
|
4953
5026
|
ctx.trackCreatedFile(fullPath);
|
|
4954
|
-
await
|
|
4955
|
-
await
|
|
5027
|
+
await fs18.ensureDir(path18.dirname(fullPath));
|
|
5028
|
+
await fs18.writeFile(fullPath, content);
|
|
4956
5029
|
count++;
|
|
4957
5030
|
}
|
|
5031
|
+
await registerRoute(projectRoot, "comingSoon", "/coming-soon", ctx);
|
|
4958
5032
|
await setConfigFlag11(projectRoot, ctx);
|
|
4959
5033
|
await ctx.commit();
|
|
4960
5034
|
log.success(`Generated coming soon feature with ${count} files`);
|
|
@@ -4973,12 +5047,12 @@ async function generateComingSoon(projectRoot) {
|
|
|
4973
5047
|
}
|
|
4974
5048
|
}
|
|
4975
5049
|
async function setConfigFlag11(projectRoot, ctx) {
|
|
4976
|
-
const configPath =
|
|
4977
|
-
if (!await
|
|
5050
|
+
const configPath = path18.join(projectRoot, "src", "config", "app.config.ts");
|
|
5051
|
+
if (!await fs18.pathExists(configPath)) return;
|
|
4978
5052
|
await ctx.trackModifiedFile(configPath);
|
|
4979
|
-
const content = await
|
|
5053
|
+
const content = await fs18.readFile(configPath, "utf-8");
|
|
4980
5054
|
const updated = content.replace(/comingSoon:\s*false/, "comingSoon: true");
|
|
4981
|
-
await
|
|
5055
|
+
await fs18.writeFile(configPath, updated);
|
|
4982
5056
|
}
|
|
4983
5057
|
function types10() {
|
|
4984
5058
|
return `${STAMP11}
|
|
@@ -5155,6 +5229,7 @@ var init_coming_soon = __esm({
|
|
|
5155
5229
|
"use strict";
|
|
5156
5230
|
init_logger();
|
|
5157
5231
|
init_rollback();
|
|
5232
|
+
init_routes();
|
|
5158
5233
|
GENERATOR_VERSION11 = "0.1.0";
|
|
5159
5234
|
STAMP11 = `// @mars-generated coming-soon@${GENERATOR_VERSION11}`;
|
|
5160
5235
|
}
|
|
@@ -5165,11 +5240,11 @@ var sentry_exports = {};
|
|
|
5165
5240
|
__export(sentry_exports, {
|
|
5166
5241
|
generateSentry: () => generateSentry
|
|
5167
5242
|
});
|
|
5168
|
-
import
|
|
5169
|
-
import
|
|
5243
|
+
import fs19 from "fs-extra";
|
|
5244
|
+
import path19 from "path";
|
|
5170
5245
|
async function generateSentry(projectRoot) {
|
|
5171
|
-
const featureDir =
|
|
5172
|
-
if (await
|
|
5246
|
+
const featureDir = path19.join(projectRoot, "src", "features", "sentry");
|
|
5247
|
+
if (await fs19.pathExists(featureDir)) {
|
|
5173
5248
|
log.error("Sentry feature already exists at src/features/sentry/");
|
|
5174
5249
|
return;
|
|
5175
5250
|
}
|
|
@@ -5185,24 +5260,27 @@ async function generateSentry(projectRoot) {
|
|
|
5185
5260
|
};
|
|
5186
5261
|
let count = 0;
|
|
5187
5262
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5188
|
-
const fullPath =
|
|
5263
|
+
const fullPath = path19.join(projectRoot, filePath);
|
|
5189
5264
|
ctx.trackCreatedFile(fullPath);
|
|
5190
|
-
await
|
|
5191
|
-
await
|
|
5265
|
+
await fs19.ensureDir(path19.dirname(fullPath));
|
|
5266
|
+
await fs19.writeFile(fullPath, content);
|
|
5192
5267
|
count++;
|
|
5193
5268
|
}
|
|
5194
5269
|
await addDependencies(projectRoot, { "@sentry/nextjs": "^8.0.0" });
|
|
5270
|
+
await wireRootLayout(projectRoot, ctx);
|
|
5195
5271
|
await setConfigFlag12(projectRoot, ctx);
|
|
5196
5272
|
await ctx.commit();
|
|
5197
|
-
log.success(`Generated sentry feature
|
|
5273
|
+
log.success(`Generated and wired sentry feature (${count} files created)`);
|
|
5198
5274
|
log.blank();
|
|
5199
5275
|
log.step("src/features/sentry/ \u2014 types, client-init, server-init, ErrorBoundary");
|
|
5200
5276
|
log.blank();
|
|
5277
|
+
log.step("Wired automatically:");
|
|
5278
|
+
log.step(" \u2713 ErrorBoundary wrapping app content in root layout");
|
|
5279
|
+
log.blank();
|
|
5201
5280
|
log.warn("Optional: run npx @sentry/wizard@latest -i nextjs for full setup");
|
|
5202
5281
|
log.blank();
|
|
5203
5282
|
log.warn("Next steps:");
|
|
5204
5283
|
log.step("Set NEXT_PUBLIC_SENTRY_DSN and SENTRY_DSN environment variables");
|
|
5205
|
-
log.step("Wrap pages or layouts with <ErrorBoundary> for graceful error handling");
|
|
5206
5284
|
log.step("Import client-init and server-init in your instrumentation files");
|
|
5207
5285
|
log.blank();
|
|
5208
5286
|
} catch (error) {
|
|
@@ -5210,13 +5288,27 @@ async function generateSentry(projectRoot) {
|
|
|
5210
5288
|
throw error;
|
|
5211
5289
|
}
|
|
5212
5290
|
}
|
|
5291
|
+
async function wireRootLayout(projectRoot, ctx) {
|
|
5292
|
+
const layoutPath = path19.join(projectRoot, "src", "app", "layout.tsx");
|
|
5293
|
+
if (!await fs19.pathExists(layoutPath)) return;
|
|
5294
|
+
let content = await fs19.readFile(layoutPath, "utf-8");
|
|
5295
|
+
if (content.includes("ErrorBoundary")) return;
|
|
5296
|
+
await ctx.trackModifiedFile(layoutPath);
|
|
5297
|
+
content = `import { ErrorBoundary } from '@/features/sentry';
|
|
5298
|
+
${content}`;
|
|
5299
|
+
content = content.replace(
|
|
5300
|
+
/(<Providers>)\{children\}(<\/Providers>)/,
|
|
5301
|
+
"$1<ErrorBoundary>{children}</ErrorBoundary>$2"
|
|
5302
|
+
);
|
|
5303
|
+
await fs19.writeFile(layoutPath, content);
|
|
5304
|
+
}
|
|
5213
5305
|
async function setConfigFlag12(projectRoot, ctx) {
|
|
5214
|
-
const configPath =
|
|
5215
|
-
if (!await
|
|
5306
|
+
const configPath = path19.join(projectRoot, "src", "config", "app.config.ts");
|
|
5307
|
+
if (!await fs19.pathExists(configPath)) return;
|
|
5216
5308
|
await ctx.trackModifiedFile(configPath);
|
|
5217
|
-
const content = await
|
|
5309
|
+
const content = await fs19.readFile(configPath, "utf-8");
|
|
5218
5310
|
const updated = content.replace(/sentry:\s*false/, "sentry: true");
|
|
5219
|
-
await
|
|
5311
|
+
await fs19.writeFile(configPath, updated);
|
|
5220
5312
|
}
|
|
5221
5313
|
function types11() {
|
|
5222
5314
|
return `${STAMP12}
|
|
@@ -5361,11 +5453,11 @@ var feature_flags_exports = {};
|
|
|
5361
5453
|
__export(feature_flags_exports, {
|
|
5362
5454
|
generateFeatureFlags: () => generateFeatureFlags
|
|
5363
5455
|
});
|
|
5364
|
-
import
|
|
5365
|
-
import
|
|
5456
|
+
import fs20 from "fs-extra";
|
|
5457
|
+
import path20 from "path";
|
|
5366
5458
|
async function generateFeatureFlags(projectRoot) {
|
|
5367
|
-
const featureDir =
|
|
5368
|
-
if (await
|
|
5459
|
+
const featureDir = path20.join(projectRoot, "src", "features", "feature-flags");
|
|
5460
|
+
if (await fs20.pathExists(featureDir)) {
|
|
5369
5461
|
log.error("Feature flags feature already exists at src/features/feature-flags/");
|
|
5370
5462
|
return;
|
|
5371
5463
|
}
|
|
@@ -5384,10 +5476,10 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5384
5476
|
};
|
|
5385
5477
|
let count = 0;
|
|
5386
5478
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5387
|
-
const fullPath =
|
|
5479
|
+
const fullPath = path20.join(projectRoot, filePath);
|
|
5388
5480
|
ctx.trackCreatedFile(fullPath);
|
|
5389
|
-
await
|
|
5390
|
-
await
|
|
5481
|
+
await fs20.ensureDir(path20.dirname(fullPath));
|
|
5482
|
+
await fs20.writeFile(fullPath, content);
|
|
5391
5483
|
count++;
|
|
5392
5484
|
}
|
|
5393
5485
|
await setConfigFlag13(projectRoot, ctx);
|
|
@@ -5407,12 +5499,12 @@ async function generateFeatureFlags(projectRoot) {
|
|
|
5407
5499
|
}
|
|
5408
5500
|
}
|
|
5409
5501
|
async function setConfigFlag13(projectRoot, ctx) {
|
|
5410
|
-
const configPath =
|
|
5411
|
-
if (!await
|
|
5502
|
+
const configPath = path20.join(projectRoot, "src", "config", "app.config.ts");
|
|
5503
|
+
if (!await fs20.pathExists(configPath)) return;
|
|
5412
5504
|
await ctx.trackModifiedFile(configPath);
|
|
5413
|
-
const content = await
|
|
5505
|
+
const content = await fs20.readFile(configPath, "utf-8");
|
|
5414
5506
|
const updated = content.replace(/featureFlags:\s*false/, "featureFlags: true");
|
|
5415
|
-
await
|
|
5507
|
+
await fs20.writeFile(configPath, updated);
|
|
5416
5508
|
}
|
|
5417
5509
|
function types12() {
|
|
5418
5510
|
return `${STAMP13}
|
|
@@ -5650,30 +5742,57 @@ var init_feature_flags = __esm({
|
|
|
5650
5742
|
// src/index.ts
|
|
5651
5743
|
import { Command } from "commander";
|
|
5652
5744
|
|
|
5745
|
+
// src/utils/version.ts
|
|
5746
|
+
import { createRequire } from "module";
|
|
5747
|
+
import path from "path";
|
|
5748
|
+
import { fileURLToPath } from "url";
|
|
5749
|
+
import fs from "fs-extra";
|
|
5750
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5751
|
+
function readVersionFromPackageJson(packageDir) {
|
|
5752
|
+
const require2 = createRequire(import.meta.url);
|
|
5753
|
+
const pkg = require2(path.join(packageDir, "package.json"));
|
|
5754
|
+
const v = pkg.version?.trim();
|
|
5755
|
+
return v ? v : "0.0.0";
|
|
5756
|
+
}
|
|
5757
|
+
function getCliVersion() {
|
|
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";
|
|
5770
|
+
}
|
|
5771
|
+
|
|
5653
5772
|
// src/commands/create.ts
|
|
5654
5773
|
init_logger();
|
|
5655
|
-
import
|
|
5656
|
-
import
|
|
5774
|
+
import fs22 from "fs-extra";
|
|
5775
|
+
import path22 from "path";
|
|
5657
5776
|
import os3 from "os";
|
|
5658
5777
|
import ora from "ora";
|
|
5659
5778
|
import pc2 from "picocolors";
|
|
5660
5779
|
import prompts5 from "prompts";
|
|
5661
5780
|
|
|
5662
5781
|
// src/utils/template.ts
|
|
5663
|
-
import
|
|
5664
|
-
import { fileURLToPath } from "url";
|
|
5665
|
-
import
|
|
5666
|
-
var
|
|
5782
|
+
import path2 from "path";
|
|
5783
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5784
|
+
import fs2 from "fs-extra";
|
|
5785
|
+
var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
5667
5786
|
function getTemplatePath() {
|
|
5668
5787
|
const candidates = [
|
|
5669
5788
|
// Built bundle: __dirname = packages/cli/dist/
|
|
5670
|
-
|
|
5671
|
-
|
|
5789
|
+
path2.resolve(__dirname2, "..", "template"),
|
|
5790
|
+
path2.resolve(__dirname2, "..", "..", "..", "template"),
|
|
5672
5791
|
// Source (vitest): __dirname = packages/cli/src/utils/
|
|
5673
|
-
|
|
5792
|
+
path2.resolve(__dirname2, "..", "..", "..", "..", "template")
|
|
5674
5793
|
];
|
|
5675
5794
|
for (const candidate of candidates) {
|
|
5676
|
-
if (
|
|
5795
|
+
if (fs2.existsSync(candidate)) {
|
|
5677
5796
|
return candidate;
|
|
5678
5797
|
}
|
|
5679
5798
|
}
|
|
@@ -5682,7 +5801,7 @@ function getTemplatePath() {
|
|
|
5682
5801
|
);
|
|
5683
5802
|
}
|
|
5684
5803
|
function resolveProjectPath(projectName) {
|
|
5685
|
-
return
|
|
5804
|
+
return path2.resolve(process.cwd(), projectName);
|
|
5686
5805
|
}
|
|
5687
5806
|
|
|
5688
5807
|
// src/prompts/project-info.ts
|
|
@@ -6032,7 +6151,6 @@ var DESIGN_DIRECTIONS = [
|
|
|
6032
6151
|
function getDefaultTheme() {
|
|
6033
6152
|
return {
|
|
6034
6153
|
primaryColor: COLOR_PRESETS[0].value,
|
|
6035
|
-
secondaryColor: "amber-400",
|
|
6036
6154
|
font: FONT_CHOICES[0].value,
|
|
6037
6155
|
designDirection: DESIGN_DIRECTIONS[0].value
|
|
6038
6156
|
};
|
|
@@ -6073,7 +6191,6 @@ async function promptTheme() {
|
|
|
6073
6191
|
if (response.primaryColor === void 0) return null;
|
|
6074
6192
|
return {
|
|
6075
6193
|
primaryColor: response.primaryColor,
|
|
6076
|
-
secondaryColor: "amber-400",
|
|
6077
6194
|
font: response.font,
|
|
6078
6195
|
designDirection: response.designDirection
|
|
6079
6196
|
};
|
|
@@ -6081,8 +6198,8 @@ async function promptTheme() {
|
|
|
6081
6198
|
|
|
6082
6199
|
// src/generators/scaffold.ts
|
|
6083
6200
|
init_logger();
|
|
6084
|
-
import
|
|
6085
|
-
import
|
|
6201
|
+
import fs3 from "fs-extra";
|
|
6202
|
+
import path3 from "path";
|
|
6086
6203
|
import { generateBrandCss } from "@mars-stack/ui/utils";
|
|
6087
6204
|
|
|
6088
6205
|
// src/generators/app-config.ts
|
|
@@ -6125,15 +6242,7 @@ function generateAppConfig(config) {
|
|
|
6125
6242
|
address: '',
|
|
6126
6243
|
},
|
|
6127
6244
|
theme: {
|
|
6128
|
-
primaryColor: '${config.theme.primaryColor}' as string,
|
|
6129
|
-
secondaryColor: '${config.theme.secondaryColor}' as string,
|
|
6130
6245
|
font: '${config.theme.font}' as string,
|
|
6131
|
-
designDirection: '${config.theme.designDirection}' as
|
|
6132
|
-
| 'modern-saas'
|
|
6133
|
-
| 'minimal'
|
|
6134
|
-
| 'enterprise'
|
|
6135
|
-
| 'creative'
|
|
6136
|
-
| 'dashboard',
|
|
6137
6246
|
},
|
|
6138
6247
|
features: {
|
|
6139
6248
|
${featureEntries}
|
|
@@ -6274,17 +6383,17 @@ var IGNORED_FILES = [
|
|
|
6274
6383
|
async function copyTemplateFiles(templateDir, targetDir) {
|
|
6275
6384
|
let fileCount = 0;
|
|
6276
6385
|
async function walkAndCopy(src, dest) {
|
|
6277
|
-
const entries = await
|
|
6386
|
+
const entries = await fs3.readdir(src, { withFileTypes: true });
|
|
6278
6387
|
for (const entry of entries) {
|
|
6279
|
-
const srcPath =
|
|
6280
|
-
const destPath =
|
|
6388
|
+
const srcPath = path3.join(src, entry.name);
|
|
6389
|
+
const destPath = path3.join(dest, entry.name);
|
|
6281
6390
|
if (entry.isDirectory()) {
|
|
6282
6391
|
if (IGNORED_DIRS.includes(entry.name)) continue;
|
|
6283
|
-
await
|
|
6392
|
+
await fs3.ensureDir(destPath);
|
|
6284
6393
|
await walkAndCopy(srcPath, destPath);
|
|
6285
6394
|
} else {
|
|
6286
6395
|
if (IGNORED_FILES.includes(entry.name)) continue;
|
|
6287
|
-
await
|
|
6396
|
+
await fs3.copy(srcPath, destPath);
|
|
6288
6397
|
fileCount++;
|
|
6289
6398
|
}
|
|
6290
6399
|
}
|
|
@@ -6295,19 +6404,19 @@ async function copyTemplateFiles(templateDir, targetDir) {
|
|
|
6295
6404
|
function resolveMarsPackageRange(packageName, templateDir) {
|
|
6296
6405
|
const shortName = packageName.replace("@mars-stack/", "");
|
|
6297
6406
|
const candidates = [
|
|
6298
|
-
|
|
6299
|
-
|
|
6407
|
+
path3.resolve(templateDir, "..", "packages", shortName, "package.json"),
|
|
6408
|
+
path3.resolve(templateDir, "..", "..", "packages", shortName, "package.json")
|
|
6300
6409
|
];
|
|
6301
6410
|
for (const candidate of candidates) {
|
|
6302
|
-
if (
|
|
6303
|
-
const pkg =
|
|
6411
|
+
if (fs3.existsSync(candidate)) {
|
|
6412
|
+
const pkg = fs3.readJsonSync(candidate);
|
|
6304
6413
|
if (typeof pkg.version === "string") {
|
|
6305
6414
|
const [major, minor] = pkg.version.split(".");
|
|
6306
6415
|
return `^${major}.${minor}.0`;
|
|
6307
6416
|
}
|
|
6308
6417
|
}
|
|
6309
6418
|
}
|
|
6310
|
-
const templatePkg =
|
|
6419
|
+
const templatePkg = fs3.readJsonSync(path3.join(templateDir, "package.json"));
|
|
6311
6420
|
const existing = templatePkg.dependencies?.[packageName];
|
|
6312
6421
|
if (existing && existing !== "*") {
|
|
6313
6422
|
if (/^[\^~>=]/.test(existing)) return existing;
|
|
@@ -6318,8 +6427,8 @@ function resolveMarsPackageRange(packageName, templateDir) {
|
|
|
6318
6427
|
}
|
|
6319
6428
|
function generatePackageJson(config) {
|
|
6320
6429
|
const templateDir = getTemplatePath();
|
|
6321
|
-
const templatePkg =
|
|
6322
|
-
const pkg =
|
|
6430
|
+
const templatePkg = path3.join(templateDir, "package.json");
|
|
6431
|
+
const pkg = fs3.readJsonSync(templatePkg);
|
|
6323
6432
|
pkg.name = config.name;
|
|
6324
6433
|
pkg.version = "0.1.0";
|
|
6325
6434
|
pkg.private = true;
|
|
@@ -6518,9 +6627,9 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6518
6627
|
const flagValue = features[feature];
|
|
6519
6628
|
if (flagValue !== false) continue;
|
|
6520
6629
|
for (const relativePath of paths) {
|
|
6521
|
-
const fullPath =
|
|
6522
|
-
if (await
|
|
6523
|
-
await
|
|
6630
|
+
const fullPath = path3.join(targetDir, relativePath);
|
|
6631
|
+
if (await fs3.pathExists(fullPath)) {
|
|
6632
|
+
await fs3.remove(fullPath);
|
|
6524
6633
|
log.step(`Pruned disabled feature path: ${relativePath}`);
|
|
6525
6634
|
}
|
|
6526
6635
|
}
|
|
@@ -6529,43 +6638,43 @@ async function pruneDisabledFeatures(features, targetDir) {
|
|
|
6529
6638
|
}
|
|
6530
6639
|
}
|
|
6531
6640
|
if (relationsToRemove.length > 0) {
|
|
6532
|
-
const authPath =
|
|
6533
|
-
if (await
|
|
6534
|
-
let content = await
|
|
6641
|
+
const authPath = path3.join(targetDir, "prisma", "schema", "auth.prisma");
|
|
6642
|
+
if (await fs3.pathExists(authPath)) {
|
|
6643
|
+
let content = await fs3.readFile(authPath, "utf-8");
|
|
6535
6644
|
for (const field of relationsToRemove) {
|
|
6536
6645
|
content = content.replace(new RegExp(`\\s*${field}\\s+\\S+.*\\n`, "g"), "\n");
|
|
6537
6646
|
}
|
|
6538
|
-
await
|
|
6647
|
+
await fs3.writeFile(authPath, content);
|
|
6539
6648
|
}
|
|
6540
6649
|
}
|
|
6541
6650
|
}
|
|
6542
6651
|
async function scaffoldProject(targetDir, config) {
|
|
6543
6652
|
const templateDir = getTemplatePath();
|
|
6544
|
-
if (!await
|
|
6653
|
+
if (!await fs3.pathExists(templateDir)) {
|
|
6545
6654
|
throw new Error(`Template directory not found: ${templateDir}`);
|
|
6546
6655
|
}
|
|
6547
|
-
await
|
|
6656
|
+
await fs3.ensureDir(targetDir);
|
|
6548
6657
|
const fileCount = await copyTemplateFiles(templateDir, targetDir);
|
|
6549
6658
|
await pruneDisabledFeatures(config.features, targetDir);
|
|
6550
|
-
await
|
|
6551
|
-
await
|
|
6552
|
-
|
|
6659
|
+
await fs3.writeFile(path3.join(targetDir, "package.json"), generatePackageJson(config));
|
|
6660
|
+
await fs3.writeFile(
|
|
6661
|
+
path3.join(targetDir, "src", "config", "app.config.ts"),
|
|
6553
6662
|
generateAppConfig(config)
|
|
6554
6663
|
);
|
|
6555
|
-
await
|
|
6556
|
-
await
|
|
6557
|
-
await
|
|
6558
|
-
await
|
|
6559
|
-
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());
|
|
6560
6669
|
await patchGlobalsCssForScaffoldedProject(targetDir, config);
|
|
6561
6670
|
await generateBrandCssForProject(targetDir, config);
|
|
6562
6671
|
return { fileCount };
|
|
6563
6672
|
}
|
|
6564
6673
|
async function generateBrandCssForProject(targetDir, config) {
|
|
6565
|
-
const brandPath =
|
|
6566
|
-
if (!await
|
|
6674
|
+
const brandPath = path3.join(targetDir, "src", "styles", "brand.css");
|
|
6675
|
+
if (!await fs3.pathExists(brandPath)) return;
|
|
6567
6676
|
const css = generateBrandCss(config.theme.primaryColor);
|
|
6568
|
-
await
|
|
6677
|
+
await fs3.writeFile(brandPath, css);
|
|
6569
6678
|
}
|
|
6570
6679
|
var VALID_DESIGN_DIRECTIONS = [
|
|
6571
6680
|
"modern-saas",
|
|
@@ -6575,9 +6684,9 @@ var VALID_DESIGN_DIRECTIONS = [
|
|
|
6575
6684
|
"dashboard"
|
|
6576
6685
|
];
|
|
6577
6686
|
async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
6578
|
-
const globalsPath =
|
|
6579
|
-
if (!await
|
|
6580
|
-
let content = await
|
|
6687
|
+
const globalsPath = path3.join(targetDir, "src", "styles", "globals.css");
|
|
6688
|
+
if (!await fs3.pathExists(globalsPath)) return;
|
|
6689
|
+
let content = await fs3.readFile(globalsPath, "utf-8");
|
|
6581
6690
|
content = content.replace(
|
|
6582
6691
|
'@source "../../../node_modules/@mars-stack/ui/dist/**/*.js";',
|
|
6583
6692
|
'@source "../../node_modules/@mars-stack/ui/dist/**/*.js";'
|
|
@@ -6596,7 +6705,7 @@ async function patchGlobalsCssForScaffoldedProject(targetDir, config) {
|
|
|
6596
6705
|
${newImport}`
|
|
6597
6706
|
);
|
|
6598
6707
|
}
|
|
6599
|
-
await
|
|
6708
|
+
await fs3.writeFile(globalsPath, content);
|
|
6600
6709
|
}
|
|
6601
6710
|
|
|
6602
6711
|
// src/generators/generate-selected.ts
|
|
@@ -6712,13 +6821,13 @@ async function generateSelectedFeatures(projectRoot, features) {
|
|
|
6712
6821
|
}
|
|
6713
6822
|
|
|
6714
6823
|
// src/utils/telemetry.ts
|
|
6715
|
-
import
|
|
6716
|
-
import
|
|
6824
|
+
import fs21 from "fs-extra";
|
|
6825
|
+
import path21 from "path";
|
|
6717
6826
|
import os2 from "os";
|
|
6718
|
-
var RC_PATH =
|
|
6827
|
+
var RC_PATH = path21.join(os2.homedir(), ".marsrc");
|
|
6719
6828
|
function isTelemetryEnabled() {
|
|
6720
6829
|
try {
|
|
6721
|
-
const config =
|
|
6830
|
+
const config = fs21.readJsonSync(RC_PATH);
|
|
6722
6831
|
return config.enabled === true;
|
|
6723
6832
|
} catch {
|
|
6724
6833
|
return false;
|
|
@@ -6727,16 +6836,16 @@ function isTelemetryEnabled() {
|
|
|
6727
6836
|
function enableTelemetry() {
|
|
6728
6837
|
const config = loadOrCreateConfig();
|
|
6729
6838
|
config.enabled = true;
|
|
6730
|
-
|
|
6839
|
+
fs21.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
6731
6840
|
}
|
|
6732
6841
|
function disableTelemetry() {
|
|
6733
6842
|
const config = loadOrCreateConfig();
|
|
6734
6843
|
config.enabled = false;
|
|
6735
|
-
|
|
6844
|
+
fs21.writeJsonSync(RC_PATH, config, { spaces: 2 });
|
|
6736
6845
|
}
|
|
6737
6846
|
function loadOrCreateConfig() {
|
|
6738
6847
|
try {
|
|
6739
|
-
return
|
|
6848
|
+
return fs21.readJsonSync(RC_PATH);
|
|
6740
6849
|
} catch {
|
|
6741
6850
|
return { enabled: false, anonymousId: crypto.randomUUID() };
|
|
6742
6851
|
}
|
|
@@ -6756,13 +6865,13 @@ function trackEvent(event, properties) {
|
|
|
6756
6865
|
},
|
|
6757
6866
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6758
6867
|
};
|
|
6759
|
-
const logPath =
|
|
6760
|
-
|
|
6868
|
+
const logPath = path21.join(os2.homedir(), ".mars-telemetry.log");
|
|
6869
|
+
fs21.appendFile(logPath, JSON.stringify(payload) + "\n").catch(() => {
|
|
6761
6870
|
});
|
|
6762
6871
|
}
|
|
6763
6872
|
|
|
6764
6873
|
// src/commands/create.ts
|
|
6765
|
-
var RC_PATH2 =
|
|
6874
|
+
var RC_PATH2 = path22.join(os3.homedir(), ".marsrc");
|
|
6766
6875
|
var PROJECT_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
6767
6876
|
var MAX_PROJECT_NAME_LENGTH = 214;
|
|
6768
6877
|
async function createCommand(projectName, options) {
|
|
@@ -6789,8 +6898,8 @@ async function createCommand(projectName, options) {
|
|
|
6789
6898
|
const projectInfo = useDefaults ? getDefaultProjectInfo(projectName) : await promptProjectInfo(projectName);
|
|
6790
6899
|
if (!projectInfo) return;
|
|
6791
6900
|
const targetDir = resolveProjectPath(projectInfo.name);
|
|
6792
|
-
if (await
|
|
6793
|
-
const entries = await
|
|
6901
|
+
if (await fs22.pathExists(targetDir)) {
|
|
6902
|
+
const entries = await fs22.readdir(targetDir);
|
|
6794
6903
|
if (entries.length > 0) {
|
|
6795
6904
|
log.error(`Directory "${projectInfo.name}" already exists and is not empty.`);
|
|
6796
6905
|
return;
|
|
@@ -6831,7 +6940,7 @@ async function createCommand(projectName, options) {
|
|
|
6831
6940
|
log.info("Scaffolding cancelled.");
|
|
6832
6941
|
process.exit(0);
|
|
6833
6942
|
}
|
|
6834
|
-
if (!
|
|
6943
|
+
if (!fs22.pathExistsSync(RC_PATH2)) {
|
|
6835
6944
|
const { telemetryOptIn } = await prompts5(
|
|
6836
6945
|
{
|
|
6837
6946
|
type: "confirm",
|
|
@@ -6870,10 +6979,10 @@ async function createCommand(projectName, options) {
|
|
|
6870
6979
|
} catch (err) {
|
|
6871
6980
|
spinner.fail("Failed to scaffold project");
|
|
6872
6981
|
log.error(err instanceof Error ? err.message : String(err));
|
|
6873
|
-
if (await
|
|
6982
|
+
if (await fs22.pathExists(targetDir)) {
|
|
6874
6983
|
const cleanupSpinner = ora("Cleaning up...").start();
|
|
6875
6984
|
try {
|
|
6876
|
-
await
|
|
6985
|
+
await fs22.remove(targetDir);
|
|
6877
6986
|
cleanupSpinner.succeed("Cleaned up partial scaffold");
|
|
6878
6987
|
} catch {
|
|
6879
6988
|
cleanupSpinner.warn(`Could not clean up ${targetDir}. You may need to remove it manually.`);
|
|
@@ -6888,8 +6997,8 @@ function countEnabled(flags) {
|
|
|
6888
6997
|
|
|
6889
6998
|
// src/commands/doctor.ts
|
|
6890
6999
|
init_logger();
|
|
6891
|
-
import
|
|
6892
|
-
import
|
|
7000
|
+
import fs23 from "fs-extra";
|
|
7001
|
+
import path23 from "path";
|
|
6893
7002
|
import { execSync } from "child_process";
|
|
6894
7003
|
function commandExists(cmd) {
|
|
6895
7004
|
try {
|
|
@@ -6909,12 +7018,12 @@ function getVersion(cmd) {
|
|
|
6909
7018
|
async function checkForUpgrades() {
|
|
6910
7019
|
log.blank();
|
|
6911
7020
|
log.title("Upgrade Check");
|
|
6912
|
-
const packageJsonPath =
|
|
6913
|
-
if (!
|
|
7021
|
+
const packageJsonPath = path23.join(process.cwd(), "package.json");
|
|
7022
|
+
if (!fs23.pathExistsSync(packageJsonPath)) {
|
|
6914
7023
|
log.warn("No package.json found \u2014 skipping upgrade check.");
|
|
6915
7024
|
return;
|
|
6916
7025
|
}
|
|
6917
|
-
const packageJson =
|
|
7026
|
+
const packageJson = fs23.readJsonSync(packageJsonPath);
|
|
6918
7027
|
const deps = packageJson.dependencies ?? {};
|
|
6919
7028
|
const devDeps = packageJson.devDependencies ?? {};
|
|
6920
7029
|
const currentRaw = deps["@mars-stack/core"] ?? devDeps["@mars-stack/core"];
|
|
@@ -6976,25 +7085,25 @@ async function doctorCommand(options) {
|
|
|
6976
7085
|
},
|
|
6977
7086
|
{
|
|
6978
7087
|
name: "package.json exists",
|
|
6979
|
-
check: () =>
|
|
7088
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), "package.json"))
|
|
6980
7089
|
},
|
|
6981
7090
|
{
|
|
6982
7091
|
name: ".env file exists",
|
|
6983
|
-
check: () =>
|
|
7092
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), ".env"))
|
|
6984
7093
|
},
|
|
6985
7094
|
{
|
|
6986
7095
|
name: "Prisma schema exists",
|
|
6987
|
-
check: () =>
|
|
7096
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), "prisma", "schema")) || fs23.pathExistsSync(path23.join(process.cwd(), "prisma", "schema.prisma"))
|
|
6988
7097
|
},
|
|
6989
7098
|
{
|
|
6990
7099
|
name: "node_modules installed",
|
|
6991
|
-
check: () =>
|
|
7100
|
+
check: () => fs23.pathExistsSync(path23.join(process.cwd(), "node_modules"))
|
|
6992
7101
|
},
|
|
6993
7102
|
{
|
|
6994
7103
|
name: "DATABASE_URL set",
|
|
6995
7104
|
check: () => {
|
|
6996
7105
|
try {
|
|
6997
|
-
const envContent =
|
|
7106
|
+
const envContent = fs23.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
|
|
6998
7107
|
const match = envContent.match(/^DATABASE_URL=(.+)$/m);
|
|
6999
7108
|
return match ? match[1] !== "" : false;
|
|
7000
7109
|
} catch {
|
|
@@ -7006,7 +7115,7 @@ async function doctorCommand(options) {
|
|
|
7006
7115
|
name: "JWT_SECRET set",
|
|
7007
7116
|
check: () => {
|
|
7008
7117
|
try {
|
|
7009
|
-
const envContent =
|
|
7118
|
+
const envContent = fs23.readFileSync(path23.join(process.cwd(), ".env"), "utf-8");
|
|
7010
7119
|
const match = envContent.match(/^JWT_SECRET=(.+)$/m);
|
|
7011
7120
|
if (!match || !match[1]) return false;
|
|
7012
7121
|
return match[1].length >= 32 ? true : "set but too short (need >=32 chars)";
|
|
@@ -7051,16 +7160,16 @@ async function doctorCommand(options) {
|
|
|
7051
7160
|
|
|
7052
7161
|
// src/commands/add.ts
|
|
7053
7162
|
init_logger();
|
|
7054
|
-
import
|
|
7055
|
-
import
|
|
7163
|
+
import fs25 from "fs-extra";
|
|
7164
|
+
import path25 from "path";
|
|
7056
7165
|
import pc3 from "picocolors";
|
|
7057
7166
|
|
|
7058
7167
|
// src/utils/doc-updater.ts
|
|
7059
|
-
import
|
|
7060
|
-
import
|
|
7168
|
+
import fs24 from "fs-extra";
|
|
7169
|
+
import path24 from "path";
|
|
7061
7170
|
async function appendToDbSchema(projectDir, modelName, fields) {
|
|
7062
|
-
const filePath =
|
|
7063
|
-
if (!await
|
|
7171
|
+
const filePath = path24.join(projectDir, "docs", "generated", "db-schema.md");
|
|
7172
|
+
if (!await fs24.pathExists(filePath)) return;
|
|
7064
7173
|
const entry = [
|
|
7065
7174
|
"",
|
|
7066
7175
|
`### ${modelName}`,
|
|
@@ -7070,12 +7179,12 @@ async function appendToDbSchema(projectDir, modelName, fields) {
|
|
|
7070
7179
|
...fields.map((f) => `| ${f} | |`),
|
|
7071
7180
|
""
|
|
7072
7181
|
].join("\n");
|
|
7073
|
-
await
|
|
7182
|
+
await fs24.appendFile(filePath, entry);
|
|
7074
7183
|
}
|
|
7075
7184
|
async function updateQualityScore(projectDir, domain, grade) {
|
|
7076
|
-
const filePath =
|
|
7077
|
-
if (!await
|
|
7078
|
-
const content = await
|
|
7185
|
+
const filePath = path24.join(projectDir, "docs", "QUALITY_SCORE.md");
|
|
7186
|
+
if (!await fs24.pathExists(filePath)) return;
|
|
7187
|
+
const content = await fs24.readFile(filePath, "utf-8");
|
|
7079
7188
|
const pattern = new RegExp(`^\\|\\s*${escapeRegex(domain)}\\s*\\|`, "m");
|
|
7080
7189
|
if (pattern.test(content)) {
|
|
7081
7190
|
const updated = content.replace(pattern, (match) => {
|
|
@@ -7083,11 +7192,11 @@ async function updateQualityScore(projectDir, domain, grade) {
|
|
|
7083
7192
|
parts[2] = ` ${grade} `;
|
|
7084
7193
|
return parts.join("|");
|
|
7085
7194
|
});
|
|
7086
|
-
await
|
|
7195
|
+
await fs24.writeFile(filePath, updated);
|
|
7087
7196
|
} else {
|
|
7088
7197
|
const row = `| ${domain} | ${grade} | |
|
|
7089
7198
|
`;
|
|
7090
|
-
await
|
|
7199
|
+
await fs24.appendFile(filePath, row);
|
|
7091
7200
|
}
|
|
7092
7201
|
}
|
|
7093
7202
|
function escapeRegex(str) {
|
|
@@ -7098,8 +7207,8 @@ function escapeRegex(str) {
|
|
|
7098
7207
|
init_rollback();
|
|
7099
7208
|
function ensureInProject() {
|
|
7100
7209
|
const cwd = process.cwd();
|
|
7101
|
-
const configPath =
|
|
7102
|
-
if (!
|
|
7210
|
+
const configPath = path25.join(cwd, "src", "config", "app.config.ts");
|
|
7211
|
+
if (!fs25.pathExistsSync(configPath)) {
|
|
7103
7212
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7104
7213
|
process.exit(1);
|
|
7105
7214
|
}
|
|
@@ -7132,8 +7241,8 @@ async function addFeatureCommand(name) {
|
|
|
7132
7241
|
const kebab = toKebab(name);
|
|
7133
7242
|
const pascal = toPascal(name);
|
|
7134
7243
|
const camel = toCamel(name);
|
|
7135
|
-
const featureDir =
|
|
7136
|
-
if (await
|
|
7244
|
+
const featureDir = path25.join(root, "src", "features", kebab);
|
|
7245
|
+
if (await fs25.pathExists(featureDir)) {
|
|
7137
7246
|
log.error(`Feature "${kebab}" already exists at src/features/${kebab}/`);
|
|
7138
7247
|
return;
|
|
7139
7248
|
}
|
|
@@ -7198,9 +7307,9 @@ export type Update${pascal}Input = z.infer<typeof ${camel}Schemas.update>;
|
|
|
7198
7307
|
ctx.trackCreatedFile(featureDir);
|
|
7199
7308
|
let count = 0;
|
|
7200
7309
|
for (const [filePath, content] of Object.entries(files)) {
|
|
7201
|
-
const fullPath =
|
|
7202
|
-
await
|
|
7203
|
-
await
|
|
7310
|
+
const fullPath = path25.join(featureDir, filePath);
|
|
7311
|
+
await fs25.ensureDir(path25.dirname(fullPath));
|
|
7312
|
+
await fs25.writeFile(fullPath, content);
|
|
7204
7313
|
count++;
|
|
7205
7314
|
}
|
|
7206
7315
|
await ctx.commit();
|
|
@@ -7221,8 +7330,8 @@ async function addPageCommand(routePath, options) {
|
|
|
7221
7330
|
const root = ensureInProject();
|
|
7222
7331
|
const cleanPath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
|
|
7223
7332
|
const group = options.protected ? "(protected)" : "(public)";
|
|
7224
|
-
const pageDir =
|
|
7225
|
-
if (await
|
|
7333
|
+
const pageDir = path25.join(root, "src", "app", group, cleanPath);
|
|
7334
|
+
if (await fs25.pathExists(path25.join(pageDir, "page.tsx"))) {
|
|
7226
7335
|
log.error(`Page already exists at src/app/${group}/${cleanPath}/page.tsx`);
|
|
7227
7336
|
return;
|
|
7228
7337
|
}
|
|
@@ -7270,9 +7379,9 @@ export default function Loading() {
|
|
|
7270
7379
|
const ctx = createRollbackContext();
|
|
7271
7380
|
try {
|
|
7272
7381
|
ctx.trackCreatedFile(pageDir);
|
|
7273
|
-
await
|
|
7274
|
-
await
|
|
7275
|
-
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);
|
|
7276
7385
|
await ctx.commit();
|
|
7277
7386
|
log.success(`Created page at ${pc3.bold(`src/app/${group}/${cleanPath}/`)}`);
|
|
7278
7387
|
trackEvent("add", { type: "page" });
|
|
@@ -7289,9 +7398,9 @@ async function addModelCommand(name) {
|
|
|
7289
7398
|
const pascal = toPascal(name);
|
|
7290
7399
|
const camel = toCamel(name);
|
|
7291
7400
|
const kebab = toKebab(name);
|
|
7292
|
-
const schemaDir =
|
|
7293
|
-
const schemaFile =
|
|
7294
|
-
if (await
|
|
7401
|
+
const schemaDir = path25.join(root, "prisma", "schema");
|
|
7402
|
+
const schemaFile = path25.join(schemaDir, `${kebab}.prisma`);
|
|
7403
|
+
if (await fs25.pathExists(schemaFile)) {
|
|
7295
7404
|
log.error(`Schema file already exists: prisma/schema/${kebab}.prisma`);
|
|
7296
7405
|
return;
|
|
7297
7406
|
}
|
|
@@ -7309,8 +7418,8 @@ async function addModelCommand(name) {
|
|
|
7309
7418
|
const ctx = createRollbackContext();
|
|
7310
7419
|
try {
|
|
7311
7420
|
ctx.trackCreatedFile(schemaFile);
|
|
7312
|
-
await
|
|
7313
|
-
await
|
|
7421
|
+
await fs25.ensureDir(schemaDir);
|
|
7422
|
+
await fs25.writeFile(schemaFile, content);
|
|
7314
7423
|
await ctx.commit();
|
|
7315
7424
|
log.success(`Created model ${pc3.bold(pascal)} at prisma/schema/${kebab}.prisma`);
|
|
7316
7425
|
trackEvent("add", { type: "model" });
|
|
@@ -7332,9 +7441,9 @@ async function addEmailCommand(name) {
|
|
|
7332
7441
|
const kebab = toKebab(name);
|
|
7333
7442
|
const pascal = toPascal(name);
|
|
7334
7443
|
const camel = toCamel(name);
|
|
7335
|
-
const templatesDir =
|
|
7336
|
-
const filePath =
|
|
7337
|
-
if (await
|
|
7444
|
+
const templatesDir = path25.join(root, "src", "lib", "core", "email", "templates");
|
|
7445
|
+
const filePath = path25.join(templatesDir, `${kebab}-email.ts`);
|
|
7446
|
+
if (await fs25.pathExists(filePath)) {
|
|
7338
7447
|
log.error(`Email template already exists: src/lib/core/email/templates/${kebab}-email.ts`);
|
|
7339
7448
|
return;
|
|
7340
7449
|
}
|
|
@@ -7378,25 +7487,25 @@ export function ${functionName}({ appName, actionUrl, userName }: ${pascal}Email
|
|
|
7378
7487
|
}
|
|
7379
7488
|
`;
|
|
7380
7489
|
const ctx = createRollbackContext();
|
|
7381
|
-
const indexPath =
|
|
7490
|
+
const indexPath = path25.join(templatesDir, "index.ts");
|
|
7382
7491
|
try {
|
|
7383
7492
|
ctx.trackCreatedFile(filePath);
|
|
7384
|
-
if (await
|
|
7493
|
+
if (await fs25.pathExists(indexPath)) {
|
|
7385
7494
|
await ctx.trackModifiedFile(indexPath);
|
|
7386
7495
|
} else {
|
|
7387
7496
|
ctx.trackCreatedFile(indexPath);
|
|
7388
7497
|
}
|
|
7389
|
-
await
|
|
7390
|
-
await
|
|
7498
|
+
await fs25.ensureDir(templatesDir);
|
|
7499
|
+
await fs25.writeFile(filePath, content);
|
|
7391
7500
|
const exportLine = `export { ${functionName} } from './${kebab}-email';
|
|
7392
7501
|
`;
|
|
7393
|
-
if (await
|
|
7394
|
-
const existing = await
|
|
7502
|
+
if (await fs25.pathExists(indexPath)) {
|
|
7503
|
+
const existing = await fs25.readFile(indexPath, "utf-8");
|
|
7395
7504
|
if (!existing.includes(functionName)) {
|
|
7396
|
-
await
|
|
7505
|
+
await fs25.appendFile(indexPath, exportLine);
|
|
7397
7506
|
}
|
|
7398
7507
|
} else {
|
|
7399
|
-
await
|
|
7508
|
+
await fs25.writeFile(indexPath, exportLine);
|
|
7400
7509
|
}
|
|
7401
7510
|
await ctx.commit();
|
|
7402
7511
|
log.success(`Created email template ${pc3.bold(kebab)} at src/lib/core/email/templates/${kebab}-email.ts`);
|
|
@@ -7419,10 +7528,10 @@ async function addComponentCommand(name, options) {
|
|
|
7419
7528
|
log.error(`Invalid type "${type}". Use: ${validTypes.join(", ")}`);
|
|
7420
7529
|
return;
|
|
7421
7530
|
}
|
|
7422
|
-
const dir = type === "primitive" ?
|
|
7423
|
-
await
|
|
7424
|
-
const filePath =
|
|
7425
|
-
if (await
|
|
7531
|
+
const dir = type === "primitive" ? path25.join(root, "src", "components", "primitives") : path25.join(root, "src", "components", "patterns");
|
|
7532
|
+
await fs25.ensureDir(dir);
|
|
7533
|
+
const filePath = path25.join(dir, `${pascal}.tsx`);
|
|
7534
|
+
if (await fs25.pathExists(filePath)) {
|
|
7426
7535
|
log.error(`Component already exists: ${pascal}.tsx`);
|
|
7427
7536
|
return;
|
|
7428
7537
|
}
|
|
@@ -7471,9 +7580,9 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7471
7580
|
const ctx = createRollbackContext();
|
|
7472
7581
|
try {
|
|
7473
7582
|
ctx.trackCreatedFile(filePath);
|
|
7474
|
-
await
|
|
7583
|
+
await fs25.writeFile(filePath, content);
|
|
7475
7584
|
await ctx.commit();
|
|
7476
|
-
log.success(`Created ${type} component ${pc3.bold(pascal)} at ${
|
|
7585
|
+
log.success(`Created ${type} component ${pc3.bold(pascal)} at ${path25.relative(root, filePath)}`);
|
|
7477
7586
|
trackEvent("add", { type: "component", componentType: type });
|
|
7478
7587
|
log.blank();
|
|
7479
7588
|
log.warn(`Remember to add the export to the barrel file:`);
|
|
@@ -7488,14 +7597,14 @@ export function ${pascal}({ children, className }: ${pascal}Props) {
|
|
|
7488
7597
|
// src/commands/configure.ts
|
|
7489
7598
|
init_logger();
|
|
7490
7599
|
import { execSync as execSync2 } from "child_process";
|
|
7491
|
-
import
|
|
7492
|
-
import
|
|
7600
|
+
import fs26 from "fs-extra";
|
|
7601
|
+
import path26 from "path";
|
|
7493
7602
|
import pc4 from "picocolors";
|
|
7494
7603
|
import prompts6 from "prompts";
|
|
7495
7604
|
function ensureInProject2() {
|
|
7496
7605
|
const cwd = process.cwd();
|
|
7497
|
-
const configPath =
|
|
7498
|
-
if (!
|
|
7606
|
+
const configPath = path26.join(cwd, "src", "config", "app.config.ts");
|
|
7607
|
+
if (!fs26.pathExistsSync(configPath)) {
|
|
7499
7608
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7500
7609
|
process.exit(1);
|
|
7501
7610
|
}
|
|
@@ -7509,8 +7618,8 @@ var PROVIDER_DEPENDENCIES = {
|
|
|
7509
7618
|
"storage:s3": ["@aws-sdk/client-s3", "@aws-sdk/s3-request-presigner"]
|
|
7510
7619
|
};
|
|
7511
7620
|
function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
7512
|
-
const configPath =
|
|
7513
|
-
let content =
|
|
7621
|
+
const configPath = path26.join(projectDir, "src", "config", "app.config.ts");
|
|
7622
|
+
let content = fs26.readFileSync(configPath, "utf-8");
|
|
7514
7623
|
if (serviceKey === "auth") {
|
|
7515
7624
|
const boolValue = provider === "google" ? "true" : "false";
|
|
7516
7625
|
const featureRegex = new RegExp(`(googleOAuth\\s*:\\s*)(?:true|false)`);
|
|
@@ -7525,10 +7634,10 @@ function updateAppConfig(projectDir, serviceKey, provider, featureKey) {
|
|
|
7525
7634
|
content = content.replace(featureRegex, `$1true`);
|
|
7526
7635
|
}
|
|
7527
7636
|
}
|
|
7528
|
-
|
|
7637
|
+
fs26.writeFileSync(configPath, content);
|
|
7529
7638
|
}
|
|
7530
7639
|
function detectPackageManager(projectDir) {
|
|
7531
|
-
if (
|
|
7640
|
+
if (fs26.existsSync(path26.join(projectDir, "yarn.lock"))) return "yarn";
|
|
7532
7641
|
return "npm";
|
|
7533
7642
|
}
|
|
7534
7643
|
function installDependencies(projectDir, deps) {
|
|
@@ -7638,15 +7747,15 @@ async function configureCommand(service) {
|
|
|
7638
7747
|
log.step(`Manually set ${pc4.bold(`services.${serviceConfig.configKey}.provider`)} to ${pc4.bold(`'${provider}'`)} in ${pc4.dim("src/config/app.config.ts")}`);
|
|
7639
7748
|
}
|
|
7640
7749
|
if (selectedProvider.envVars.length > 0) {
|
|
7641
|
-
const envPath =
|
|
7642
|
-
if (await
|
|
7643
|
-
const envContent = await
|
|
7750
|
+
const envPath = path26.join(root, ".env");
|
|
7751
|
+
if (await fs26.pathExists(envPath)) {
|
|
7752
|
+
const envContent = await fs26.readFile(envPath, "utf-8");
|
|
7644
7753
|
const missingVars = selectedProvider.envVars.filter(
|
|
7645
7754
|
(v) => !envContent.includes(v)
|
|
7646
7755
|
);
|
|
7647
7756
|
if (missingVars.length > 0) {
|
|
7648
7757
|
const additions = missingVars.map((v) => `${v}=""`).join("\n");
|
|
7649
|
-
await
|
|
7758
|
+
await fs26.appendFile(envPath, `
|
|
7650
7759
|
# ${selectedService} (${provider})
|
|
7651
7760
|
${additions}
|
|
7652
7761
|
`);
|
|
@@ -7675,15 +7784,15 @@ ${additions}
|
|
|
7675
7784
|
|
|
7676
7785
|
// src/commands/deploy.ts
|
|
7677
7786
|
init_logger();
|
|
7678
|
-
import
|
|
7679
|
-
import
|
|
7787
|
+
import fs27 from "fs-extra";
|
|
7788
|
+
import path27 from "path";
|
|
7680
7789
|
import { execSync as execSync3 } from "child_process";
|
|
7681
7790
|
import pc5 from "picocolors";
|
|
7682
7791
|
import prompts7 from "prompts";
|
|
7683
7792
|
function ensureInProject3() {
|
|
7684
7793
|
const cwd = process.cwd();
|
|
7685
|
-
const configPath =
|
|
7686
|
-
if (!
|
|
7794
|
+
const configPath = path27.join(cwd, "src", "config", "app.config.ts");
|
|
7795
|
+
if (!fs27.pathExistsSync(configPath)) {
|
|
7687
7796
|
log.error("Not inside a MARS project. Run this command from the project root.");
|
|
7688
7797
|
process.exit(1);
|
|
7689
7798
|
}
|
|
@@ -7722,8 +7831,8 @@ async function deployCommand() {
|
|
|
7722
7831
|
return;
|
|
7723
7832
|
}
|
|
7724
7833
|
}
|
|
7725
|
-
const vercelDir =
|
|
7726
|
-
if (!
|
|
7834
|
+
const vercelDir = path27.join(root, ".vercel");
|
|
7835
|
+
if (!fs27.existsSync(vercelDir)) {
|
|
7727
7836
|
log.step("Linking project to Vercel...");
|
|
7728
7837
|
try {
|
|
7729
7838
|
execSync3("vercel link", { cwd: root, stdio: "inherit" });
|
|
@@ -7740,8 +7849,8 @@ async function deployCommand() {
|
|
|
7740
7849
|
log.blank();
|
|
7741
7850
|
const requiredVars = ["JWT_SECRET", "DATABASE_URL"];
|
|
7742
7851
|
log.step(`Core: ${pc5.dim(requiredVars.join(", "))}`);
|
|
7743
|
-
const configPath =
|
|
7744
|
-
const configContent = await
|
|
7852
|
+
const configPath = path27.join(root, "src", "config", "app.config.ts");
|
|
7853
|
+
const configContent = await fs27.readFile(configPath, "utf-8");
|
|
7745
7854
|
if (configContent.includes("email: { provider: 'sendgrid'")) {
|
|
7746
7855
|
log.step(`Email (SendGrid): ${pc5.dim("SENDGRID_API_KEY, SENDGRID_FROM_EMAIL")}`);
|
|
7747
7856
|
} else if (configContent.includes("email: { provider: 'resend'")) {
|
|
@@ -7802,8 +7911,8 @@ async function deployCommand() {
|
|
|
7802
7911
|
|
|
7803
7912
|
// src/commands/upgrade.ts
|
|
7804
7913
|
init_logger();
|
|
7805
|
-
import
|
|
7806
|
-
import
|
|
7914
|
+
import fs28 from "fs-extra";
|
|
7915
|
+
import path28 from "path";
|
|
7807
7916
|
import { execSync as execSync4 } from "child_process";
|
|
7808
7917
|
var MARS_PACKAGES = ["@mars-stack/core", "@mars-stack/ui"];
|
|
7809
7918
|
var CHANGELOG_URL = "https://github.com/greaveselliott/mars/blob/main/CHANGELOG.md";
|
|
@@ -7826,7 +7935,7 @@ function readCurrentVersion(packageJson, packageName) {
|
|
|
7826
7935
|
return version ? version.replace(/^[\^~]/, "") : null;
|
|
7827
7936
|
}
|
|
7828
7937
|
function detectPackageManager2(projectDir) {
|
|
7829
|
-
if (
|
|
7938
|
+
if (fs28.pathExistsSync(path28.join(projectDir, "yarn.lock"))) {
|
|
7830
7939
|
return "yarn";
|
|
7831
7940
|
}
|
|
7832
7941
|
return "npm";
|
|
@@ -7890,7 +7999,7 @@ function updatePackageJsonVersions(packageJsonPath, packageJson, versions) {
|
|
|
7890
7999
|
if (updated.length > 0) {
|
|
7891
8000
|
packageJson.dependencies = deps;
|
|
7892
8001
|
packageJson.devDependencies = devDeps;
|
|
7893
|
-
|
|
8002
|
+
fs28.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
7894
8003
|
}
|
|
7895
8004
|
return updated;
|
|
7896
8005
|
}
|
|
@@ -7900,15 +8009,15 @@ function upgradeCommand(program2) {
|
|
|
7900
8009
|
).option("--dry-run", "Show what would be updated without making changes").action(async (options) => {
|
|
7901
8010
|
log.title("MARS Upgrade");
|
|
7902
8011
|
const projectDir = process.cwd();
|
|
7903
|
-
const packageJsonPath =
|
|
7904
|
-
if (!
|
|
8012
|
+
const packageJsonPath = path28.join(projectDir, "package.json");
|
|
8013
|
+
if (!fs28.pathExistsSync(packageJsonPath)) {
|
|
7905
8014
|
log.error(
|
|
7906
8015
|
"No package.json found. Are you in a Mars project directory?"
|
|
7907
8016
|
);
|
|
7908
8017
|
process.exitCode = 1;
|
|
7909
8018
|
return;
|
|
7910
8019
|
}
|
|
7911
|
-
const packageJson =
|
|
8020
|
+
const packageJson = fs28.readJsonSync(packageJsonPath);
|
|
7912
8021
|
const hasMarsPackage = MARS_PACKAGES.some(
|
|
7913
8022
|
(name) => readCurrentVersion(packageJson, name) !== null
|
|
7914
8023
|
);
|
|
@@ -8004,7 +8113,7 @@ init_sentry();
|
|
|
8004
8113
|
init_feature_flags();
|
|
8005
8114
|
init_logger();
|
|
8006
8115
|
var program = new Command();
|
|
8007
|
-
program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(
|
|
8116
|
+
program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(getCliVersion()).option("-v, --verbose", "Enable verbose output for debugging");
|
|
8008
8117
|
function isVerbose() {
|
|
8009
8118
|
return program.opts().verbose === true;
|
|
8010
8119
|
}
|