create-stackforge 0.1.3 → 0.1.4
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/cli.js +524 -512
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -711,8 +711,7 @@ async function createProjectSkeleton(root, config, ctx) {
|
|
|
711
711
|
}
|
|
712
712
|
|
|
713
713
|
// src/generators/database/database-files.ts
|
|
714
|
-
import { join as
|
|
715
|
-
import { fileURLToPath } from "url";
|
|
714
|
+
import { join as join5 } from "path";
|
|
716
715
|
|
|
717
716
|
// src/utils/env-file.ts
|
|
718
717
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -753,12 +752,30 @@ async function removeEnvKey(path, key, ctx) {
|
|
|
753
752
|
await writeFile3(path, filtered.join("\n"), "utf8");
|
|
754
753
|
}
|
|
755
754
|
|
|
755
|
+
// src/utils/templates-dir.ts
|
|
756
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
757
|
+
import { existsSync as existsSync2 } from "fs";
|
|
758
|
+
import { fileURLToPath } from "url";
|
|
759
|
+
function findPackageRoot() {
|
|
760
|
+
let dir = dirname3(fileURLToPath(import.meta.url));
|
|
761
|
+
for (let i = 0; i < 10; i++) {
|
|
762
|
+
if (existsSync2(join4(dir, "package.json"))) {
|
|
763
|
+
return dir;
|
|
764
|
+
}
|
|
765
|
+
const parent = dirname3(dir);
|
|
766
|
+
if (parent === dir) break;
|
|
767
|
+
dir = parent;
|
|
768
|
+
}
|
|
769
|
+
throw new Error("Could not find package root (package.json)");
|
|
770
|
+
}
|
|
771
|
+
var TEMPLATES_DIR = join4(findPackageRoot(), "templates");
|
|
772
|
+
|
|
756
773
|
// src/generators/database/database-files.ts
|
|
757
774
|
async function generateDatabaseFiles(root, config, ctx) {
|
|
758
|
-
const projectRoot =
|
|
759
|
-
const templatesRoot =
|
|
775
|
+
const projectRoot = join5(root, config.projectName);
|
|
776
|
+
const templatesRoot = TEMPLATES_DIR;
|
|
760
777
|
if (config.database.provider !== "none") {
|
|
761
|
-
const envPath =
|
|
778
|
+
const envPath = join5(projectRoot, ".env.example");
|
|
762
779
|
if (config.database.provider === "mongodb") {
|
|
763
780
|
await appendEnvLine(envPath, 'MONGODB_URI=""', ctx);
|
|
764
781
|
} else {
|
|
@@ -767,91 +784,90 @@ async function generateDatabaseFiles(root, config, ctx) {
|
|
|
767
784
|
if (config.database.provider === "neon") {
|
|
768
785
|
await appendEnvLine(envPath, 'NEON_API_KEY=""', ctx);
|
|
769
786
|
await appendEnvLine(envPath, 'NEON_PROJECT_ID=""', ctx);
|
|
770
|
-
const providerDir =
|
|
787
|
+
const providerDir = join5(projectRoot, "database");
|
|
771
788
|
await ensureDir(providerDir, ctx);
|
|
772
|
-
const providerReadme = await readTextFile(
|
|
773
|
-
await writeTextFile(
|
|
789
|
+
const providerReadme = await readTextFile(join5(templatesRoot, "database", "providers", "neon.README.md"));
|
|
790
|
+
await writeTextFile(join5(providerDir, "README.md"), providerReadme, ctx);
|
|
774
791
|
}
|
|
775
792
|
if (config.database.provider === "supabase") {
|
|
776
793
|
await appendEnvLine(envPath, 'SUPABASE_URL=""', ctx);
|
|
777
794
|
await appendEnvLine(envPath, 'SUPABASE_ANON_KEY=""', ctx);
|
|
778
|
-
const providerDir =
|
|
795
|
+
const providerDir = join5(projectRoot, "database");
|
|
779
796
|
await ensureDir(providerDir, ctx);
|
|
780
|
-
const providerReadme = await readTextFile(
|
|
781
|
-
await writeTextFile(
|
|
797
|
+
const providerReadme = await readTextFile(join5(templatesRoot, "database", "providers", "supabase.README.md"));
|
|
798
|
+
await writeTextFile(join5(providerDir, "README.md"), providerReadme, ctx);
|
|
782
799
|
}
|
|
783
800
|
}
|
|
784
801
|
if (config.database.orm === "drizzle") {
|
|
785
|
-
const drizzleDir =
|
|
802
|
+
const drizzleDir = join5(projectRoot, "drizzle");
|
|
786
803
|
await ensureDir(drizzleDir, ctx);
|
|
787
|
-
const configContent = await readTextFile(
|
|
788
|
-
const schemaContent = await readTextFile(
|
|
804
|
+
const configContent = await readTextFile(join5(templatesRoot, "database", "drizzle", "drizzle.config.ts"));
|
|
805
|
+
const schemaContent = await readTextFile(join5(templatesRoot, "database", "drizzle", "schema.ts"));
|
|
789
806
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
790
|
-
const clientContent = await readTextFile(
|
|
791
|
-
const exampleContent = await readTextFile(
|
|
792
|
-
await writeTextFile(
|
|
793
|
-
await writeTextFile(
|
|
794
|
-
await writeTextFile(
|
|
795
|
-
await writeTextFile(
|
|
807
|
+
const clientContent = await readTextFile(join5(templatesRoot, "database", "drizzle", `client.${ext}`));
|
|
808
|
+
const exampleContent = await readTextFile(join5(templatesRoot, "database", "drizzle", `example.${ext}`));
|
|
809
|
+
await writeTextFile(join5(projectRoot, "drizzle.config.ts"), configContent, ctx);
|
|
810
|
+
await writeTextFile(join5(drizzleDir, "schema.ts"), schemaContent, ctx);
|
|
811
|
+
await writeTextFile(join5(drizzleDir, `client.${ext}`), clientContent, ctx);
|
|
812
|
+
await writeTextFile(join5(drizzleDir, `example.${ext}`), exampleContent, ctx);
|
|
796
813
|
}
|
|
797
814
|
if (config.database.orm === "prisma") {
|
|
798
|
-
const prismaDir =
|
|
815
|
+
const prismaDir = join5(projectRoot, "prisma");
|
|
799
816
|
await ensureDir(prismaDir, ctx);
|
|
800
|
-
const schema = await readTextFile(
|
|
801
|
-
await writeTextFile(
|
|
802
|
-
const dbDir =
|
|
817
|
+
const schema = await readTextFile(join5(templatesRoot, "database", "prisma", "schema.prisma"));
|
|
818
|
+
await writeTextFile(join5(prismaDir, "schema.prisma"), schema, ctx);
|
|
819
|
+
const dbDir = join5(projectRoot, "src", "db");
|
|
803
820
|
await ensureDir(dbDir, ctx);
|
|
804
821
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
805
|
-
const client = await readTextFile(
|
|
806
|
-
const example = await readTextFile(
|
|
807
|
-
await writeTextFile(
|
|
808
|
-
await writeTextFile(
|
|
809
|
-
const usage = await readTextFile(
|
|
810
|
-
await writeTextFile(
|
|
822
|
+
const client = await readTextFile(join5(templatesRoot, "database", "prisma", `client.${ext}`));
|
|
823
|
+
const example = await readTextFile(join5(templatesRoot, "database", "prisma", `example.${ext}`));
|
|
824
|
+
await writeTextFile(join5(dbDir, `prisma.${ext}`), client, ctx);
|
|
825
|
+
await writeTextFile(join5(dbDir, `prisma-example.${ext}`), example, ctx);
|
|
826
|
+
const usage = await readTextFile(join5(templatesRoot, "database", "usage", `prisma-users.${ext}`));
|
|
827
|
+
await writeTextFile(join5(dbDir, `users.${ext}`), usage, ctx);
|
|
811
828
|
}
|
|
812
829
|
if (config.database.orm === "mongoose") {
|
|
813
|
-
const dbDir =
|
|
830
|
+
const dbDir = join5(projectRoot, "src", "db");
|
|
814
831
|
await ensureDir(dbDir, ctx);
|
|
815
832
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
816
|
-
const connection = await readTextFile(
|
|
817
|
-
const model = await readTextFile(
|
|
818
|
-
await writeTextFile(
|
|
819
|
-
await writeTextFile(
|
|
820
|
-
const usage = await readTextFile(
|
|
821
|
-
await writeTextFile(
|
|
833
|
+
const connection = await readTextFile(join5(templatesRoot, "database", "mongoose", `connection.${ext}`));
|
|
834
|
+
const model = await readTextFile(join5(templatesRoot, "database", "mongoose", `model.${ext}`));
|
|
835
|
+
await writeTextFile(join5(dbDir, `mongoose.${ext}`), connection, ctx);
|
|
836
|
+
await writeTextFile(join5(dbDir, `mongoose-model.${ext}`), model, ctx);
|
|
837
|
+
const usage = await readTextFile(join5(templatesRoot, "database", "usage", `mongoose-users.${ext}`));
|
|
838
|
+
await writeTextFile(join5(dbDir, `users.${ext}`), usage, ctx);
|
|
822
839
|
}
|
|
823
840
|
if (config.database.orm === "typeorm") {
|
|
824
|
-
const dbDir =
|
|
841
|
+
const dbDir = join5(projectRoot, "src", "db");
|
|
825
842
|
await ensureDir(dbDir, ctx);
|
|
826
843
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
827
|
-
const template = await readTextFile(
|
|
844
|
+
const template = await readTextFile(join5(templatesRoot, "database", "typeorm", `data-source.${ext}`));
|
|
828
845
|
const typeormType = config.database.provider === "mysql" ? "mysql" : config.database.provider === "sqlite" ? "sqlite" : "postgres";
|
|
829
846
|
const migrationsPath = config.frontend.language === "ts" ? "migrations/*.ts" : "migrations/*.js";
|
|
830
847
|
const content = template.replace("{{typeormType}}", typeormType).replace("{{migrationsPath}}", migrationsPath);
|
|
831
|
-
await writeTextFile(
|
|
832
|
-
const entitiesDir =
|
|
848
|
+
await writeTextFile(join5(dbDir, `data-source.${ext}`), content, ctx);
|
|
849
|
+
const entitiesDir = join5(dbDir, "entities");
|
|
833
850
|
await ensureDir(entitiesDir, ctx);
|
|
834
|
-
const entity = await readTextFile(
|
|
835
|
-
await writeTextFile(
|
|
836
|
-
const migrationsDir =
|
|
851
|
+
const entity = await readTextFile(join5(templatesRoot, "database", "typeorm", `entity.${ext}`));
|
|
852
|
+
await writeTextFile(join5(entitiesDir, `User.${ext}`), entity, ctx);
|
|
853
|
+
const migrationsDir = join5(dbDir, "migrations");
|
|
837
854
|
await ensureDir(migrationsDir, ctx);
|
|
838
|
-
const migrationReadme = await readTextFile(
|
|
839
|
-
await writeTextFile(
|
|
840
|
-
const usage = await readTextFile(
|
|
841
|
-
await writeTextFile(
|
|
855
|
+
const migrationReadme = await readTextFile(join5(templatesRoot, "database", "typeorm", "migrations", "README.md"));
|
|
856
|
+
await writeTextFile(join5(migrationsDir, "README.md"), migrationReadme, ctx);
|
|
857
|
+
const usage = await readTextFile(join5(templatesRoot, "database", "usage", `typeorm-users.${ext}`));
|
|
858
|
+
await writeTextFile(join5(dbDir, `users.${ext}`), usage, ctx);
|
|
842
859
|
}
|
|
843
860
|
if (config.database.orm === "drizzle") {
|
|
844
|
-
const dbDir =
|
|
861
|
+
const dbDir = join5(projectRoot, "src", "db");
|
|
845
862
|
await ensureDir(dbDir, ctx);
|
|
846
863
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
847
|
-
const usage = await readTextFile(
|
|
848
|
-
await writeTextFile(
|
|
864
|
+
const usage = await readTextFile(join5(templatesRoot, "database", "usage", `drizzle-users.${ext}`));
|
|
865
|
+
await writeTextFile(join5(dbDir, `users.${ext}`), usage, ctx);
|
|
849
866
|
}
|
|
850
867
|
}
|
|
851
868
|
|
|
852
869
|
// src/generators/frontend/frontend-files.ts
|
|
853
|
-
import { join as
|
|
854
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
870
|
+
import { join as join6 } from "path";
|
|
855
871
|
|
|
856
872
|
// src/generators/templates/template-engine.ts
|
|
857
873
|
function applyTemplate(content, vars) {
|
|
@@ -864,10 +880,10 @@ function applyTemplate(content, vars) {
|
|
|
864
880
|
|
|
865
881
|
// src/generators/frontend/frontend-files.ts
|
|
866
882
|
async function generateFrontendFiles(root, config, ctx) {
|
|
867
|
-
const projectRoot =
|
|
868
|
-
const templatesRoot =
|
|
883
|
+
const projectRoot = join6(root, config.projectName);
|
|
884
|
+
const templatesRoot = TEMPLATES_DIR;
|
|
869
885
|
if (config.frontend.type === "nextjs") {
|
|
870
|
-
const appDir =
|
|
886
|
+
const appDir = join6(projectRoot, "app");
|
|
871
887
|
await ensureDir(appDir, ctx);
|
|
872
888
|
const uiCssImports = buildUiCssImports(config, "nextjs");
|
|
873
889
|
const importCss = uiCssImports.length ? uiCssImports.join("") + "\n" : "";
|
|
@@ -876,25 +892,25 @@ async function generateFrontendFiles(root, config, ctx) {
|
|
|
876
892
|
const hasProviders = hasTrpc || hasUiProvider;
|
|
877
893
|
const providersImport = hasProviders ? "import { Providers } from './providers';\n" : "";
|
|
878
894
|
const wrapChildren = hasProviders ? "<Providers>{children}</Providers>" : "{children}";
|
|
879
|
-
const layoutTemplatePath =
|
|
895
|
+
const layoutTemplatePath = join6(
|
|
880
896
|
templatesRoot,
|
|
881
897
|
"nextjs",
|
|
882
898
|
"app",
|
|
883
899
|
config.frontend.language === "ts" ? "layout.tsx" : "layout.jsx"
|
|
884
900
|
);
|
|
885
|
-
const pageTemplatePath =
|
|
901
|
+
const pageTemplatePath = join6(
|
|
886
902
|
templatesRoot,
|
|
887
903
|
"nextjs",
|
|
888
904
|
"app",
|
|
889
905
|
config.frontend.language === "ts" ? "page.tsx" : "page.jsx"
|
|
890
906
|
);
|
|
891
|
-
const actionsTemplatePath =
|
|
907
|
+
const actionsTemplatePath = join6(
|
|
892
908
|
templatesRoot,
|
|
893
909
|
"nextjs",
|
|
894
910
|
"app",
|
|
895
911
|
config.frontend.language === "ts" ? "actions.ts" : "actions.js"
|
|
896
912
|
);
|
|
897
|
-
const nextConfigTemplatePath =
|
|
913
|
+
const nextConfigTemplatePath = join6(
|
|
898
914
|
templatesRoot,
|
|
899
915
|
"nextjs",
|
|
900
916
|
config.frontend.language === "ts" ? "next.config.ts" : "next.config.js"
|
|
@@ -902,7 +918,7 @@ async function generateFrontendFiles(root, config, ctx) {
|
|
|
902
918
|
const layoutTemplate = await readTextFile(layoutTemplatePath);
|
|
903
919
|
const pageTemplate = await readTextFile(pageTemplatePath);
|
|
904
920
|
const examplesTemplate = await readTextFile(
|
|
905
|
-
|
|
921
|
+
join6(
|
|
906
922
|
templatesRoot,
|
|
907
923
|
"nextjs",
|
|
908
924
|
"app",
|
|
@@ -919,15 +935,15 @@ async function generateFrontendFiles(root, config, ctx) {
|
|
|
919
935
|
const links = buildPageLinks(config);
|
|
920
936
|
const page = applyTemplate(pageTemplate, { projectName: config.projectName, links });
|
|
921
937
|
await writeTextFile(
|
|
922
|
-
|
|
938
|
+
join6(projectRoot, config.frontend.language === "ts" ? "next.config.ts" : "next.config.js"),
|
|
923
939
|
nextConfigTemplate,
|
|
924
940
|
ctx
|
|
925
941
|
);
|
|
926
|
-
await writeTextFile(
|
|
927
|
-
await writeTextFile(
|
|
928
|
-
await writeTextFile(
|
|
942
|
+
await writeTextFile(join6(appDir, config.frontend.language === "ts" ? "layout.tsx" : "layout.jsx"), layout, ctx);
|
|
943
|
+
await writeTextFile(join6(appDir, config.frontend.language === "ts" ? "page.tsx" : "page.jsx"), page, ctx);
|
|
944
|
+
await writeTextFile(join6(appDir, config.frontend.language === "ts" ? "actions.ts" : "actions.js"), actionsTemplate, ctx);
|
|
929
945
|
if (config.api.type !== "none") {
|
|
930
|
-
const examplesDir =
|
|
946
|
+
const examplesDir = join6(appDir, "examples");
|
|
931
947
|
await ensureDir(examplesDir, ctx);
|
|
932
948
|
const { imports, components } = buildApiExamples(config, "nextjs");
|
|
933
949
|
const featureNotes = buildFeatureNotes(config);
|
|
@@ -940,7 +956,7 @@ async function generateFrontendFiles(root, config, ctx) {
|
|
|
940
956
|
uiDemoComponent
|
|
941
957
|
});
|
|
942
958
|
await writeTextFile(
|
|
943
|
-
|
|
959
|
+
join6(examplesDir, config.frontend.language === "ts" ? "page.tsx" : "page.jsx"),
|
|
944
960
|
examplesPage,
|
|
945
961
|
ctx
|
|
946
962
|
);
|
|
@@ -948,23 +964,23 @@ async function generateFrontendFiles(root, config, ctx) {
|
|
|
948
964
|
if (hasProviders) {
|
|
949
965
|
const providers = buildProvidersComponent(config, "nextjs");
|
|
950
966
|
await writeTextFile(
|
|
951
|
-
|
|
967
|
+
join6(appDir, config.frontend.language === "ts" ? "providers.tsx" : "providers.jsx"),
|
|
952
968
|
providers,
|
|
953
969
|
ctx
|
|
954
970
|
);
|
|
955
971
|
}
|
|
956
972
|
}
|
|
957
973
|
if (config.frontend.type === "vite") {
|
|
958
|
-
const srcDir =
|
|
974
|
+
const srcDir = join6(projectRoot, "src");
|
|
959
975
|
await ensureDir(srcDir, ctx);
|
|
960
976
|
const uiCssImports = buildUiCssImports(config, "vite");
|
|
961
977
|
const cssImport = uiCssImports.length ? uiCssImports.join("") : "";
|
|
962
978
|
const ext = config.frontend.language === "ts" ? "tsx" : "jsx";
|
|
963
|
-
const mainTemplatePath =
|
|
964
|
-
const appTemplatePath =
|
|
965
|
-
const indexTemplatePath =
|
|
966
|
-
const viteConfigTemplatePath =
|
|
967
|
-
const viteEnvTemplatePath =
|
|
979
|
+
const mainTemplatePath = join6(templatesRoot, "vite", config.frontend.language === "ts" ? "main.tsx" : "main.jsx");
|
|
980
|
+
const appTemplatePath = join6(templatesRoot, "vite", config.frontend.language === "ts" ? "App.tsx" : "App.jsx");
|
|
981
|
+
const indexTemplatePath = join6(templatesRoot, "vite", "index.html");
|
|
982
|
+
const viteConfigTemplatePath = join6(templatesRoot, "vite", "vite.config.ts");
|
|
983
|
+
const viteEnvTemplatePath = join6(templatesRoot, "vite", "vite-env.d.ts");
|
|
968
984
|
const mainTemplate = await readTextFile(mainTemplatePath);
|
|
969
985
|
const appTemplate = await readTextFile(appTemplatePath);
|
|
970
986
|
const indexTemplate = await readTextFile(indexTemplatePath);
|
|
@@ -997,17 +1013,17 @@ async function generateFrontendFiles(root, config, ctx) {
|
|
|
997
1013
|
uiDemoComponent
|
|
998
1014
|
});
|
|
999
1015
|
const indexHtml = applyTemplate(indexTemplate, { projectName: config.projectName, ext });
|
|
1000
|
-
await writeTextFile(
|
|
1001
|
-
await writeTextFile(
|
|
1002
|
-
await writeTextFile(
|
|
1003
|
-
await writeTextFile(
|
|
1016
|
+
await writeTextFile(join6(projectRoot, "index.html"), indexHtml, ctx);
|
|
1017
|
+
await writeTextFile(join6(projectRoot, "vite.config.ts"), viteConfigTemplate, ctx);
|
|
1018
|
+
await writeTextFile(join6(srcDir, config.frontend.language === "ts" ? "main.tsx" : "main.jsx"), main, ctx);
|
|
1019
|
+
await writeTextFile(join6(srcDir, config.frontend.language === "ts" ? "App.tsx" : "App.jsx"), app, ctx);
|
|
1004
1020
|
if (config.frontend.language === "ts") {
|
|
1005
|
-
await writeTextFile(
|
|
1021
|
+
await writeTextFile(join6(srcDir, "vite-env.d.ts"), viteEnvTemplate, ctx);
|
|
1006
1022
|
}
|
|
1007
|
-
await appendEnvLine(
|
|
1023
|
+
await appendEnvLine(join6(projectRoot, ".env.example"), 'VITE_API_URL="http://localhost:3001"', ctx);
|
|
1008
1024
|
if (hasProviders) {
|
|
1009
1025
|
const providers = buildProvidersComponent(config, "vite");
|
|
1010
|
-
await writeTextFile(
|
|
1026
|
+
await writeTextFile(join6(srcDir, config.frontend.language === "ts" ? "providers.tsx" : "providers.jsx"), providers, ctx);
|
|
1011
1027
|
}
|
|
1012
1028
|
}
|
|
1013
1029
|
}
|
|
@@ -1201,70 +1217,69 @@ function buildPageLinks(config) {
|
|
|
1201
1217
|
}
|
|
1202
1218
|
|
|
1203
1219
|
// src/generators/ui/ui-files.ts
|
|
1204
|
-
import { join as
|
|
1205
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1220
|
+
import { join as join7 } from "path";
|
|
1206
1221
|
async function generateUiFiles(root, config, ctx) {
|
|
1207
|
-
const projectRoot =
|
|
1208
|
-
const templatesRoot =
|
|
1222
|
+
const projectRoot = join7(root, config.projectName);
|
|
1223
|
+
const templatesRoot = TEMPLATES_DIR;
|
|
1209
1224
|
if (config.ui.library === "tailwind") {
|
|
1210
|
-
const tailwindTemplate = await readTextFile(
|
|
1211
|
-
const postcssTemplate = await readTextFile(
|
|
1212
|
-
const stylesTemplate = await readTextFile(
|
|
1213
|
-
await writeTextFile(
|
|
1214
|
-
await writeTextFile(
|
|
1215
|
-
const stylesDir =
|
|
1225
|
+
const tailwindTemplate = await readTextFile(join7(templatesRoot, "ui", "tailwind.config.js"));
|
|
1226
|
+
const postcssTemplate = await readTextFile(join7(templatesRoot, "ui", "postcss.config.js"));
|
|
1227
|
+
const stylesTemplate = await readTextFile(join7(templatesRoot, "ui", "styles.css"));
|
|
1228
|
+
await writeTextFile(join7(projectRoot, "tailwind.config.js"), tailwindTemplate, ctx);
|
|
1229
|
+
await writeTextFile(join7(projectRoot, "postcss.config.js"), postcssTemplate, ctx);
|
|
1230
|
+
const stylesDir = join7(projectRoot, "src");
|
|
1216
1231
|
await ensureDir(stylesDir, ctx);
|
|
1217
|
-
await writeTextFile(
|
|
1232
|
+
await writeTextFile(join7(stylesDir, "styles.css"), stylesTemplate, ctx);
|
|
1218
1233
|
const demo = await readTextFile(
|
|
1219
|
-
|
|
1234
|
+
join7(templatesRoot, "ui", config.frontend.language === "ts" ? "demo-tailwind.tsx" : "demo-tailwind.jsx")
|
|
1220
1235
|
);
|
|
1221
|
-
await ensureDir(
|
|
1236
|
+
await ensureDir(join7(projectRoot, "src", "components"), ctx);
|
|
1222
1237
|
await writeTextFile(
|
|
1223
|
-
|
|
1238
|
+
join7(projectRoot, "src", "components", config.frontend.language === "ts" ? "ui-demo.tsx" : "ui-demo.jsx"),
|
|
1224
1239
|
demo,
|
|
1225
1240
|
ctx
|
|
1226
1241
|
);
|
|
1227
1242
|
}
|
|
1228
1243
|
if (config.ui.library === "shadcn") {
|
|
1229
|
-
await ensureDir(
|
|
1230
|
-
const shadcnReadme = await readTextFile(
|
|
1231
|
-
await writeTextFile(
|
|
1232
|
-
const componentsJson = await readTextFile(
|
|
1233
|
-
await writeTextFile(
|
|
1234
|
-
const srcDir =
|
|
1235
|
-
await ensureDir(
|
|
1236
|
-
await ensureDir(
|
|
1244
|
+
await ensureDir(join7(projectRoot, "components"), ctx);
|
|
1245
|
+
const shadcnReadme = await readTextFile(join7(templatesRoot, "ui", "shadcn.README.md"));
|
|
1246
|
+
await writeTextFile(join7(projectRoot, "components", "README.md"), shadcnReadme, ctx);
|
|
1247
|
+
const componentsJson = await readTextFile(join7(templatesRoot, "ui", "components.json"));
|
|
1248
|
+
await writeTextFile(join7(projectRoot, "components.json"), componentsJson, ctx);
|
|
1249
|
+
const srcDir = join7(projectRoot, "src");
|
|
1250
|
+
await ensureDir(join7(srcDir, "lib"), ctx);
|
|
1251
|
+
await ensureDir(join7(srcDir, "components", "ui"), ctx);
|
|
1237
1252
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1238
|
-
const utilsTemplate = await readTextFile(
|
|
1239
|
-
await writeTextFile(
|
|
1253
|
+
const utilsTemplate = await readTextFile(join7(templatesRoot, "ui", `utils.${ext}`));
|
|
1254
|
+
await writeTextFile(join7(srcDir, "lib", `utils.${ext}`), utilsTemplate, ctx);
|
|
1240
1255
|
const buttonTemplate = await readTextFile(
|
|
1241
|
-
|
|
1256
|
+
join7(templatesRoot, "ui", config.frontend.language === "ts" ? "button.tsx" : "button.jsx")
|
|
1242
1257
|
);
|
|
1243
|
-
await writeTextFile(
|
|
1258
|
+
await writeTextFile(join7(srcDir, "components", "ui", config.frontend.language === "ts" ? "button.tsx" : "button.jsx"), buttonTemplate, ctx);
|
|
1244
1259
|
const demo = await readTextFile(
|
|
1245
|
-
|
|
1260
|
+
join7(templatesRoot, "ui", config.frontend.language === "ts" ? "demo-shadcn.tsx" : "demo-shadcn.jsx")
|
|
1246
1261
|
);
|
|
1247
1262
|
await writeTextFile(
|
|
1248
|
-
|
|
1263
|
+
join7(srcDir, "components", config.frontend.language === "ts" ? "ui-demo.tsx" : "ui-demo.jsx"),
|
|
1249
1264
|
demo,
|
|
1250
1265
|
ctx
|
|
1251
1266
|
);
|
|
1252
1267
|
}
|
|
1253
1268
|
if (["mui", "chakra", "mantine", "antd", "nextui"].includes(config.ui.library)) {
|
|
1254
|
-
await ensureDir(
|
|
1255
|
-
const readme = await readTextFile(
|
|
1256
|
-
await writeTextFile(
|
|
1257
|
-
const srcDir =
|
|
1269
|
+
await ensureDir(join7(projectRoot, "components"), ctx);
|
|
1270
|
+
const readme = await readTextFile(join7(templatesRoot, "ui", `${config.ui.library}.README.md`));
|
|
1271
|
+
await writeTextFile(join7(projectRoot, "components", "README.md"), readme, ctx);
|
|
1272
|
+
const srcDir = join7(projectRoot, "src");
|
|
1258
1273
|
await ensureDir(srcDir, ctx);
|
|
1259
1274
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1260
|
-
const themeTemplate = await readTextFile(
|
|
1261
|
-
await writeTextFile(
|
|
1275
|
+
const themeTemplate = await readTextFile(join7(templatesRoot, "ui", `${config.ui.library}.theme.${ext}`));
|
|
1276
|
+
await writeTextFile(join7(srcDir, `theme.${ext}`), themeTemplate, ctx);
|
|
1262
1277
|
const demo = await readTextFile(
|
|
1263
|
-
|
|
1278
|
+
join7(templatesRoot, "ui", `demo-${config.ui.library}.${config.frontend.language === "ts" ? "tsx" : "jsx"}`)
|
|
1264
1279
|
);
|
|
1265
|
-
await ensureDir(
|
|
1280
|
+
await ensureDir(join7(srcDir, "components"), ctx);
|
|
1266
1281
|
await writeTextFile(
|
|
1267
|
-
|
|
1282
|
+
join7(srcDir, "components", config.frontend.language === "ts" ? "ui-demo.tsx" : "ui-demo.jsx"),
|
|
1268
1283
|
demo,
|
|
1269
1284
|
ctx
|
|
1270
1285
|
);
|
|
@@ -1272,54 +1287,53 @@ async function generateUiFiles(root, config, ctx) {
|
|
|
1272
1287
|
}
|
|
1273
1288
|
|
|
1274
1289
|
// src/generators/auth/auth-files.ts
|
|
1275
|
-
import { join as
|
|
1276
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1290
|
+
import { join as join8 } from "path";
|
|
1277
1291
|
async function generateAuthFiles(root, config, ctx) {
|
|
1278
|
-
const projectRoot =
|
|
1279
|
-
const templatesRoot =
|
|
1292
|
+
const projectRoot = join8(root, config.projectName);
|
|
1293
|
+
const templatesRoot = TEMPLATES_DIR;
|
|
1280
1294
|
if (config.auth.provider === "nextauth") {
|
|
1281
|
-
await appendEnvLine(
|
|
1282
|
-
await appendEnvLine(
|
|
1295
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'NEXTAUTH_SECRET=""', ctx);
|
|
1296
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'NEXTAUTH_URL=""', ctx);
|
|
1283
1297
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1284
1298
|
if (config.frontend.type === "nextjs") {
|
|
1285
|
-
const routeDir =
|
|
1299
|
+
const routeDir = join8(projectRoot, "app", "api", "auth", "[...nextauth]");
|
|
1286
1300
|
await ensureDir(routeDir, ctx);
|
|
1287
|
-
const route = await readTextFile(
|
|
1288
|
-
await writeTextFile(
|
|
1301
|
+
const route = await readTextFile(join8(templatesRoot, "auth", `nextauth-route.${ext}`));
|
|
1302
|
+
await writeTextFile(join8(routeDir, config.frontend.language === "ts" ? "route.ts" : "route.js"), route, ctx);
|
|
1289
1303
|
}
|
|
1290
|
-
const authDir =
|
|
1304
|
+
const authDir = join8(projectRoot, "auth");
|
|
1291
1305
|
await ensureDir(authDir, ctx);
|
|
1292
|
-
const readme = await readTextFile(
|
|
1293
|
-
await writeTextFile(
|
|
1294
|
-
const options = await readTextFile(
|
|
1295
|
-
await writeTextFile(
|
|
1306
|
+
const readme = await readTextFile(join8(templatesRoot, "auth", "nextauth.README.md"));
|
|
1307
|
+
await writeTextFile(join8(authDir, "README.md"), readme, ctx);
|
|
1308
|
+
const options = await readTextFile(join8(templatesRoot, "auth", `nextauth-options.${ext}`));
|
|
1309
|
+
await writeTextFile(join8(authDir, `auth-options.${ext}`), options, ctx);
|
|
1296
1310
|
if (config.frontend.type === "nextjs") {
|
|
1297
|
-
const protectedDir =
|
|
1311
|
+
const protectedDir = join8(projectRoot, "app", "auth", "protected");
|
|
1298
1312
|
await ensureDir(protectedDir, ctx);
|
|
1299
1313
|
const protectedPage = await readTextFile(
|
|
1300
|
-
|
|
1314
|
+
join8(templatesRoot, "auth", `nextauth-protected-page.${ext}x`)
|
|
1301
1315
|
);
|
|
1302
|
-
await writeTextFile(
|
|
1303
|
-
const signInDir =
|
|
1316
|
+
await writeTextFile(join8(protectedDir, `page.${ext}x`), protectedPage, ctx);
|
|
1317
|
+
const signInDir = join8(projectRoot, "app", "auth", "signin");
|
|
1304
1318
|
await ensureDir(signInDir, ctx);
|
|
1305
|
-
const signInPage = await readTextFile(
|
|
1306
|
-
await writeTextFile(
|
|
1319
|
+
const signInPage = await readTextFile(join8(templatesRoot, "auth", `nextauth-signin.${ext}x`));
|
|
1320
|
+
await writeTextFile(join8(signInDir, `page.${ext}x`), signInPage, ctx);
|
|
1307
1321
|
} else {
|
|
1308
|
-
const protectedPage = await readTextFile(
|
|
1309
|
-
await writeTextFile(
|
|
1322
|
+
const protectedPage = await readTextFile(join8(templatesRoot, "auth", `nextauth-protected.${ext}x`));
|
|
1323
|
+
await writeTextFile(join8(authDir, `protected.${ext}x`), protectedPage, ctx);
|
|
1310
1324
|
}
|
|
1311
1325
|
}
|
|
1312
1326
|
if (config.auth.provider === "clerk") {
|
|
1313
|
-
await appendEnvLine(
|
|
1314
|
-
await appendEnvLine(
|
|
1315
|
-
const libDir =
|
|
1327
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'CLERK_SECRET_KEY=""', ctx);
|
|
1328
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=""', ctx);
|
|
1329
|
+
const libDir = join8(projectRoot, "src", "lib");
|
|
1316
1330
|
await ensureDir(libDir, ctx);
|
|
1317
1331
|
const clerkClient = `import { clerkClient } from '@clerk/nextjs/server';
|
|
1318
1332
|
|
|
1319
1333
|
export { clerkClient };
|
|
1320
1334
|
`;
|
|
1321
1335
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1322
|
-
await writeTextFile(
|
|
1336
|
+
await writeTextFile(join8(libDir, `clerk.${ext}`), clerkClient, ctx);
|
|
1323
1337
|
if (config.frontend.type === "nextjs") {
|
|
1324
1338
|
const middleware = `import { authMiddleware } from '@clerk/nextjs';
|
|
1325
1339
|
|
|
@@ -1329,63 +1343,63 @@ export const config = {
|
|
|
1329
1343
|
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
|
|
1330
1344
|
};
|
|
1331
1345
|
`;
|
|
1332
|
-
await writeTextFile(
|
|
1346
|
+
await writeTextFile(join8(projectRoot, `middleware.${ext}`), middleware, ctx);
|
|
1333
1347
|
}
|
|
1334
|
-
const authDir =
|
|
1348
|
+
const authDir = join8(projectRoot, "auth");
|
|
1335
1349
|
await ensureDir(authDir, ctx);
|
|
1336
|
-
const readme = await readTextFile(
|
|
1337
|
-
await writeTextFile(
|
|
1350
|
+
const readme = await readTextFile(join8(templatesRoot, "auth", "clerk.README.md"));
|
|
1351
|
+
await writeTextFile(join8(authDir, "README.md"), readme, ctx);
|
|
1338
1352
|
if (config.frontend.type === "nextjs") {
|
|
1339
|
-
const protectedDir =
|
|
1353
|
+
const protectedDir = join8(projectRoot, "app", "auth", "protected");
|
|
1340
1354
|
await ensureDir(protectedDir, ctx);
|
|
1341
1355
|
const protectedPage = await readTextFile(
|
|
1342
|
-
|
|
1356
|
+
join8(templatesRoot, "auth", `clerk-protected-page.${ext}x`)
|
|
1343
1357
|
);
|
|
1344
|
-
await writeTextFile(
|
|
1345
|
-
const signInDir =
|
|
1358
|
+
await writeTextFile(join8(protectedDir, `page.${ext}x`), protectedPage, ctx);
|
|
1359
|
+
const signInDir = join8(projectRoot, "app", "auth", "signin");
|
|
1346
1360
|
await ensureDir(signInDir, ctx);
|
|
1347
|
-
const signInPage = await readTextFile(
|
|
1348
|
-
await writeTextFile(
|
|
1361
|
+
const signInPage = await readTextFile(join8(templatesRoot, "auth", `clerk-signin.${ext}x`));
|
|
1362
|
+
await writeTextFile(join8(signInDir, `page.${ext}x`), signInPage, ctx);
|
|
1349
1363
|
} else {
|
|
1350
|
-
const protectedPage = await readTextFile(
|
|
1351
|
-
await writeTextFile(
|
|
1364
|
+
const protectedPage = await readTextFile(join8(templatesRoot, "auth", `clerk-protected.${ext}x`));
|
|
1365
|
+
await writeTextFile(join8(authDir, `protected.${ext}x`), protectedPage, ctx);
|
|
1352
1366
|
}
|
|
1353
1367
|
}
|
|
1354
1368
|
if (config.auth.provider === "better-auth") {
|
|
1355
|
-
await appendEnvLine(
|
|
1356
|
-
await appendEnvLine(
|
|
1369
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'BETTER_AUTH_SECRET=""', ctx);
|
|
1370
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'BETTER_AUTH_URL=""', ctx);
|
|
1357
1371
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1358
|
-
const authDir =
|
|
1372
|
+
const authDir = join8(projectRoot, "auth");
|
|
1359
1373
|
await ensureDir(authDir, ctx);
|
|
1360
|
-
const readme = await readTextFile(
|
|
1361
|
-
await writeTextFile(
|
|
1362
|
-
const serverConfig = await readTextFile(
|
|
1363
|
-
await writeTextFile(
|
|
1364
|
-
const libDir =
|
|
1374
|
+
const readme = await readTextFile(join8(templatesRoot, "auth", "better-auth.README.md"));
|
|
1375
|
+
await writeTextFile(join8(authDir, "README.md"), readme, ctx);
|
|
1376
|
+
const serverConfig = await readTextFile(join8(templatesRoot, "auth", `better-auth-server.${ext}`));
|
|
1377
|
+
await writeTextFile(join8(authDir, `auth.${ext}`), serverConfig, ctx);
|
|
1378
|
+
const libDir = join8(projectRoot, "src", "lib");
|
|
1365
1379
|
await ensureDir(libDir, ctx);
|
|
1366
|
-
const clientConfig = await readTextFile(
|
|
1367
|
-
await writeTextFile(
|
|
1380
|
+
const clientConfig = await readTextFile(join8(templatesRoot, "auth", `better-auth-client.${ext}`));
|
|
1381
|
+
await writeTextFile(join8(libDir, `auth-client.${ext}`), clientConfig, ctx);
|
|
1368
1382
|
if (config.frontend.type === "nextjs") {
|
|
1369
|
-
const routeDir =
|
|
1383
|
+
const routeDir = join8(projectRoot, "app", "api", "auth", "[...all]");
|
|
1370
1384
|
await ensureDir(routeDir, ctx);
|
|
1371
|
-
const route = await readTextFile(
|
|
1372
|
-
await writeTextFile(
|
|
1373
|
-
const protectedDir =
|
|
1385
|
+
const route = await readTextFile(join8(templatesRoot, "auth", `better-auth-route.${ext}`));
|
|
1386
|
+
await writeTextFile(join8(routeDir, `route.${ext}`), route, ctx);
|
|
1387
|
+
const protectedDir = join8(projectRoot, "app", "auth", "protected");
|
|
1374
1388
|
await ensureDir(protectedDir, ctx);
|
|
1375
1389
|
const protectedPage = await readTextFile(
|
|
1376
|
-
|
|
1390
|
+
join8(templatesRoot, "auth", `better-auth-protected-page.${ext}x`)
|
|
1377
1391
|
);
|
|
1378
|
-
await writeTextFile(
|
|
1379
|
-
const signInDir =
|
|
1392
|
+
await writeTextFile(join8(protectedDir, `page.${ext}x`), protectedPage, ctx);
|
|
1393
|
+
const signInDir = join8(projectRoot, "app", "auth", "signin");
|
|
1380
1394
|
await ensureDir(signInDir, ctx);
|
|
1381
|
-
const signInPage = await readTextFile(
|
|
1382
|
-
await writeTextFile(
|
|
1395
|
+
const signInPage = await readTextFile(join8(templatesRoot, "auth", `better-auth-signin.${ext}x`));
|
|
1396
|
+
await writeTextFile(join8(signInDir, `page.${ext}x`), signInPage, ctx);
|
|
1383
1397
|
}
|
|
1384
1398
|
}
|
|
1385
1399
|
if (config.auth.provider === "supabase") {
|
|
1386
|
-
await appendEnvLine(
|
|
1387
|
-
await appendEnvLine(
|
|
1388
|
-
const libDir =
|
|
1400
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'NEXT_PUBLIC_SUPABASE_URL=""', ctx);
|
|
1401
|
+
await appendEnvLine(join8(projectRoot, ".env.example"), 'NEXT_PUBLIC_SUPABASE_ANON_KEY=""', ctx);
|
|
1402
|
+
const libDir = join8(projectRoot, "src", "lib");
|
|
1389
1403
|
await ensureDir(libDir, ctx);
|
|
1390
1404
|
const supabaseClient = `import { createClient } from '@supabase/supabase-js';
|
|
1391
1405
|
|
|
@@ -1395,7 +1409,7 @@ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
|
|
|
1395
1409
|
export const supabase = createClient(url, key);
|
|
1396
1410
|
`;
|
|
1397
1411
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1398
|
-
await writeTextFile(
|
|
1412
|
+
await writeTextFile(join8(libDir, `supabase.${ext}`), supabaseClient, ctx);
|
|
1399
1413
|
if (config.frontend.type === "nextjs") {
|
|
1400
1414
|
const supabaseServer = `import { createServerClient } from '@supabase/ssr';
|
|
1401
1415
|
import { cookies } from 'next/headers';
|
|
@@ -1415,47 +1429,46 @@ export function createSupabaseServerClient() {
|
|
|
1415
1429
|
);
|
|
1416
1430
|
}
|
|
1417
1431
|
`;
|
|
1418
|
-
await writeTextFile(
|
|
1432
|
+
await writeTextFile(join8(libDir, `supabase-server.${ext}`), supabaseServer, ctx);
|
|
1419
1433
|
}
|
|
1420
|
-
const authDir =
|
|
1434
|
+
const authDir = join8(projectRoot, "auth");
|
|
1421
1435
|
await ensureDir(authDir, ctx);
|
|
1422
|
-
const readme = await readTextFile(
|
|
1423
|
-
await writeTextFile(
|
|
1436
|
+
const readme = await readTextFile(join8(templatesRoot, "auth", "supabase.README.md"));
|
|
1437
|
+
await writeTextFile(join8(authDir, "README.md"), readme, ctx);
|
|
1424
1438
|
if (config.frontend.type === "nextjs") {
|
|
1425
|
-
const protectedDir =
|
|
1439
|
+
const protectedDir = join8(projectRoot, "app", "auth", "protected");
|
|
1426
1440
|
await ensureDir(protectedDir, ctx);
|
|
1427
1441
|
const protectedPage = await readTextFile(
|
|
1428
|
-
|
|
1442
|
+
join8(templatesRoot, "auth", `supabase-protected-page.${ext}x`)
|
|
1429
1443
|
);
|
|
1430
|
-
await writeTextFile(
|
|
1431
|
-
const signInDir =
|
|
1444
|
+
await writeTextFile(join8(protectedDir, `page.${ext}x`), protectedPage, ctx);
|
|
1445
|
+
const signInDir = join8(projectRoot, "app", "auth", "signin");
|
|
1432
1446
|
await ensureDir(signInDir, ctx);
|
|
1433
|
-
const signInPage = await readTextFile(
|
|
1434
|
-
await writeTextFile(
|
|
1447
|
+
const signInPage = await readTextFile(join8(templatesRoot, "auth", `supabase-signin.${ext}x`));
|
|
1448
|
+
await writeTextFile(join8(signInDir, `page.${ext}x`), signInPage, ctx);
|
|
1435
1449
|
} else {
|
|
1436
|
-
const protectedPage = await readTextFile(
|
|
1437
|
-
await writeTextFile(
|
|
1450
|
+
const protectedPage = await readTextFile(join8(templatesRoot, "auth", `supabase-protected.${ext}x`));
|
|
1451
|
+
await writeTextFile(join8(authDir, `protected.${ext}x`), protectedPage, ctx);
|
|
1438
1452
|
const signin = await readTextFile(
|
|
1439
|
-
|
|
1453
|
+
join8(templatesRoot, "auth", `supabase-vite-signin.${ext}x`)
|
|
1440
1454
|
);
|
|
1441
|
-
const authUiDir =
|
|
1455
|
+
const authUiDir = join8(projectRoot, "src", "auth");
|
|
1442
1456
|
await ensureDir(authUiDir, ctx);
|
|
1443
|
-
await writeTextFile(
|
|
1457
|
+
await writeTextFile(join8(authUiDir, `signin.${ext}x`), signin, ctx);
|
|
1444
1458
|
}
|
|
1445
1459
|
}
|
|
1446
1460
|
}
|
|
1447
1461
|
|
|
1448
1462
|
// src/generators/api/api-files.ts
|
|
1449
|
-
import { join as
|
|
1450
|
-
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1463
|
+
import { join as join9 } from "path";
|
|
1451
1464
|
async function generateApiFiles(root, config, ctx) {
|
|
1452
|
-
const projectRoot =
|
|
1453
|
-
const templatesRoot =
|
|
1465
|
+
const projectRoot = join9(root, config.projectName);
|
|
1466
|
+
const templatesRoot = TEMPLATES_DIR;
|
|
1454
1467
|
if (config.api.type === "rest") {
|
|
1455
|
-
const apiDir =
|
|
1468
|
+
const apiDir = join9(projectRoot, "api");
|
|
1456
1469
|
await ensureDir(apiDir, ctx);
|
|
1457
1470
|
await writeTextFile(
|
|
1458
|
-
|
|
1471
|
+
join9(apiDir, "README.md"),
|
|
1459
1472
|
`# REST API
|
|
1460
1473
|
|
|
1461
1474
|
## Overview
|
|
@@ -1471,56 +1484,56 @@ async function generateApiFiles(root, config, ctx) {
|
|
|
1471
1484
|
ctx
|
|
1472
1485
|
);
|
|
1473
1486
|
if (config.frontend.type === "nextjs") {
|
|
1474
|
-
const routeDir =
|
|
1487
|
+
const routeDir = join9(projectRoot, "app", "api", "hello");
|
|
1475
1488
|
await ensureDir(routeDir, ctx);
|
|
1476
1489
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1477
|
-
const handler = await readTextFile(
|
|
1478
|
-
await writeTextFile(
|
|
1490
|
+
const handler = await readTextFile(join9(templatesRoot, "api", "rest", `route.${ext}`));
|
|
1491
|
+
await writeTextFile(join9(routeDir, `route.${ext}`), handler, ctx);
|
|
1479
1492
|
if (config.database.orm) {
|
|
1480
|
-
const usersDir =
|
|
1493
|
+
const usersDir = join9(projectRoot, "app", "api", "users");
|
|
1481
1494
|
await ensureDir(usersDir, ctx);
|
|
1482
1495
|
const usersRoute = await readTextFile(
|
|
1483
|
-
|
|
1496
|
+
join9(templatesRoot, "api", "rest", config.frontend.language === "ts" ? "users-route.ts" : "users-route.js")
|
|
1484
1497
|
);
|
|
1485
1498
|
await writeTextFile(
|
|
1486
|
-
|
|
1499
|
+
join9(usersDir, config.frontend.language === "ts" ? "route.ts" : "route.js"),
|
|
1487
1500
|
usersRoute,
|
|
1488
1501
|
ctx
|
|
1489
1502
|
);
|
|
1490
1503
|
}
|
|
1491
1504
|
}
|
|
1492
1505
|
if (config.frontend.type === "vite") {
|
|
1493
|
-
const serverDir =
|
|
1506
|
+
const serverDir = join9(projectRoot, "src", "server");
|
|
1494
1507
|
await ensureDir(serverDir, ctx);
|
|
1495
1508
|
const serverTemplate = await readTextFile(
|
|
1496
|
-
|
|
1509
|
+
join9(templatesRoot, "api", "rest", config.frontend.language === "ts" ? "vite-server.ts" : "vite-server.js")
|
|
1497
1510
|
);
|
|
1498
1511
|
await writeTextFile(
|
|
1499
|
-
|
|
1512
|
+
join9(serverDir, config.frontend.language === "ts" ? "index.ts" : "index.js"),
|
|
1500
1513
|
serverTemplate,
|
|
1501
1514
|
ctx
|
|
1502
1515
|
);
|
|
1503
1516
|
}
|
|
1504
|
-
const clientDir =
|
|
1517
|
+
const clientDir = join9(projectRoot, "src", "api");
|
|
1505
1518
|
await ensureDir(clientDir, ctx);
|
|
1506
1519
|
const client = await readTextFile(
|
|
1507
|
-
|
|
1520
|
+
join9(templatesRoot, "api", "rest", config.frontend.language === "ts" ? "client.ts" : "client.js")
|
|
1508
1521
|
);
|
|
1509
|
-
await writeTextFile(
|
|
1522
|
+
await writeTextFile(join9(clientDir, config.frontend.language === "ts" ? "client.ts" : "client.js"), client, ctx);
|
|
1510
1523
|
const usage = await readTextFile(
|
|
1511
|
-
|
|
1524
|
+
join9(templatesRoot, "api", "rest", config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx")
|
|
1512
1525
|
);
|
|
1513
1526
|
await writeTextFile(
|
|
1514
|
-
|
|
1527
|
+
join9(clientDir, config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx"),
|
|
1515
1528
|
usage,
|
|
1516
1529
|
ctx
|
|
1517
1530
|
);
|
|
1518
1531
|
}
|
|
1519
1532
|
if (config.api.type === "trpc") {
|
|
1520
|
-
const apiDir =
|
|
1533
|
+
const apiDir = join9(projectRoot, "api");
|
|
1521
1534
|
await ensureDir(apiDir, ctx);
|
|
1522
1535
|
await writeTextFile(
|
|
1523
|
-
|
|
1536
|
+
join9(apiDir, "README.md"),
|
|
1524
1537
|
`# tRPC Setup
|
|
1525
1538
|
|
|
1526
1539
|
## Overview
|
|
@@ -1536,69 +1549,69 @@ async function generateApiFiles(root, config, ctx) {
|
|
|
1536
1549
|
ctx
|
|
1537
1550
|
);
|
|
1538
1551
|
if (config.frontend.type === "nextjs") {
|
|
1539
|
-
const trpcDir =
|
|
1552
|
+
const trpcDir = join9(projectRoot, "src", "server", "api");
|
|
1540
1553
|
await ensureDir(trpcDir, ctx);
|
|
1541
|
-
const router = await readTextFile(
|
|
1542
|
-
const appRouter = await readTextFile(
|
|
1543
|
-
await writeTextFile(
|
|
1544
|
-
await writeTextFile(
|
|
1545
|
-
const routeDir =
|
|
1554
|
+
const router = await readTextFile(join9(templatesRoot, "api", "trpc", "trpc.ts"));
|
|
1555
|
+
const appRouter = await readTextFile(join9(templatesRoot, "api", "trpc", "root.ts"));
|
|
1556
|
+
await writeTextFile(join9(trpcDir, "trpc.ts"), router, ctx);
|
|
1557
|
+
await writeTextFile(join9(trpcDir, "root.ts"), appRouter, ctx);
|
|
1558
|
+
const routeDir = join9(projectRoot, "app", "api", "trpc", "[trpc]");
|
|
1546
1559
|
await ensureDir(routeDir, ctx);
|
|
1547
|
-
const handler = await readTextFile(
|
|
1548
|
-
await writeTextFile(
|
|
1549
|
-
const clientDir =
|
|
1560
|
+
const handler = await readTextFile(join9(templatesRoot, "api", "trpc", "route.ts"));
|
|
1561
|
+
await writeTextFile(join9(routeDir, config.frontend.language === "ts" ? "route.ts" : "route.js"), handler, ctx);
|
|
1562
|
+
const clientDir = join9(projectRoot, "src", "trpc");
|
|
1550
1563
|
await ensureDir(clientDir, ctx);
|
|
1551
1564
|
const client = await readTextFile(
|
|
1552
|
-
|
|
1565
|
+
join9(templatesRoot, "api", "trpc", config.frontend.language === "ts" ? "client.ts" : "client.js")
|
|
1553
1566
|
);
|
|
1554
|
-
await writeTextFile(
|
|
1567
|
+
await writeTextFile(join9(clientDir, config.frontend.language === "ts" ? "client.ts" : "client.js"), client, ctx);
|
|
1555
1568
|
const usage = await readTextFile(
|
|
1556
|
-
|
|
1569
|
+
join9(templatesRoot, "api", "trpc", config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx")
|
|
1557
1570
|
);
|
|
1558
1571
|
await writeTextFile(
|
|
1559
|
-
|
|
1572
|
+
join9(clientDir, config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx"),
|
|
1560
1573
|
usage,
|
|
1561
1574
|
ctx
|
|
1562
1575
|
);
|
|
1563
1576
|
}
|
|
1564
1577
|
if (config.frontend.type === "vite") {
|
|
1565
|
-
const trpcDir =
|
|
1578
|
+
const trpcDir = join9(projectRoot, "src", "server", "api");
|
|
1566
1579
|
await ensureDir(trpcDir, ctx);
|
|
1567
|
-
const router = await readTextFile(
|
|
1568
|
-
const appRouter = await readTextFile(
|
|
1569
|
-
await writeTextFile(
|
|
1570
|
-
await writeTextFile(
|
|
1571
|
-
const clientDir =
|
|
1580
|
+
const router = await readTextFile(join9(templatesRoot, "api", "trpc", "trpc.ts"));
|
|
1581
|
+
const appRouter = await readTextFile(join9(templatesRoot, "api", "trpc", "root.ts"));
|
|
1582
|
+
await writeTextFile(join9(trpcDir, "trpc.ts"), router, ctx);
|
|
1583
|
+
await writeTextFile(join9(trpcDir, "root.ts"), appRouter, ctx);
|
|
1584
|
+
const clientDir = join9(projectRoot, "src", "trpc");
|
|
1572
1585
|
await ensureDir(clientDir, ctx);
|
|
1573
1586
|
const client = await readTextFile(
|
|
1574
|
-
|
|
1587
|
+
join9(templatesRoot, "api", "trpc", config.frontend.language === "ts" ? "client-vite.ts" : "client-vite.js")
|
|
1575
1588
|
);
|
|
1576
|
-
await writeTextFile(
|
|
1589
|
+
await writeTextFile(join9(clientDir, config.frontend.language === "ts" ? "client.ts" : "client.js"), client, ctx);
|
|
1577
1590
|
const usage = await readTextFile(
|
|
1578
|
-
|
|
1591
|
+
join9(templatesRoot, "api", "trpc", config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx")
|
|
1579
1592
|
);
|
|
1580
1593
|
await writeTextFile(
|
|
1581
|
-
|
|
1594
|
+
join9(clientDir, config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx"),
|
|
1582
1595
|
usage,
|
|
1583
1596
|
ctx
|
|
1584
1597
|
);
|
|
1585
|
-
const serverDir =
|
|
1598
|
+
const serverDir = join9(projectRoot, "src", "server");
|
|
1586
1599
|
await ensureDir(serverDir, ctx);
|
|
1587
1600
|
const serverTemplate = await readTextFile(
|
|
1588
|
-
|
|
1601
|
+
join9(templatesRoot, "api", "trpc", config.frontend.language === "ts" ? "vite-server.ts" : "vite-server.js")
|
|
1589
1602
|
);
|
|
1590
1603
|
await writeTextFile(
|
|
1591
|
-
|
|
1604
|
+
join9(serverDir, config.frontend.language === "ts" ? "index.ts" : "index.js"),
|
|
1592
1605
|
serverTemplate,
|
|
1593
1606
|
ctx
|
|
1594
1607
|
);
|
|
1595
1608
|
}
|
|
1596
1609
|
}
|
|
1597
1610
|
if (config.api.type === "graphql") {
|
|
1598
|
-
const apiDir =
|
|
1611
|
+
const apiDir = join9(projectRoot, "api");
|
|
1599
1612
|
await ensureDir(apiDir, ctx);
|
|
1600
1613
|
await writeTextFile(
|
|
1601
|
-
|
|
1614
|
+
join9(apiDir, "README.md"),
|
|
1602
1615
|
`# GraphQL Setup
|
|
1603
1616
|
|
|
1604
1617
|
## Overview
|
|
@@ -1614,54 +1627,54 @@ async function generateApiFiles(root, config, ctx) {
|
|
|
1614
1627
|
ctx
|
|
1615
1628
|
);
|
|
1616
1629
|
if (config.frontend.type === "nextjs") {
|
|
1617
|
-
const gqlDir =
|
|
1630
|
+
const gqlDir = join9(projectRoot, "src", "graphql");
|
|
1618
1631
|
await ensureDir(gqlDir, ctx);
|
|
1619
|
-
const schema = await readTextFile(
|
|
1620
|
-
await writeTextFile(
|
|
1621
|
-
const routeDir =
|
|
1632
|
+
const schema = await readTextFile(join9(templatesRoot, "api", "graphql", "schema.graphql"));
|
|
1633
|
+
await writeTextFile(join9(gqlDir, "schema.graphql"), schema, ctx);
|
|
1634
|
+
const routeDir = join9(projectRoot, "app", "api", "graphql");
|
|
1622
1635
|
await ensureDir(routeDir, ctx);
|
|
1623
1636
|
const gqlExt = config.frontend.language === "ts" ? "ts" : "js";
|
|
1624
|
-
const handler = await readTextFile(
|
|
1625
|
-
await writeTextFile(
|
|
1626
|
-
const clientDir =
|
|
1637
|
+
const handler = await readTextFile(join9(templatesRoot, "api", "graphql", `route.${gqlExt}`));
|
|
1638
|
+
await writeTextFile(join9(routeDir, `route.${gqlExt}`), handler, ctx);
|
|
1639
|
+
const clientDir = join9(projectRoot, "src", "graphql");
|
|
1627
1640
|
const client = await readTextFile(
|
|
1628
|
-
|
|
1641
|
+
join9(templatesRoot, "api", "graphql", config.frontend.language === "ts" ? "client.ts" : "client.js")
|
|
1629
1642
|
);
|
|
1630
|
-
await writeTextFile(
|
|
1643
|
+
await writeTextFile(join9(clientDir, config.frontend.language === "ts" ? "client.ts" : "client.js"), client, ctx);
|
|
1631
1644
|
const usage = await readTextFile(
|
|
1632
|
-
|
|
1645
|
+
join9(templatesRoot, "api", "graphql", config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx")
|
|
1633
1646
|
);
|
|
1634
1647
|
await writeTextFile(
|
|
1635
|
-
|
|
1648
|
+
join9(clientDir, config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx"),
|
|
1636
1649
|
usage,
|
|
1637
1650
|
ctx
|
|
1638
1651
|
);
|
|
1639
1652
|
}
|
|
1640
1653
|
if (config.frontend.type === "vite") {
|
|
1641
|
-
const gqlDir =
|
|
1654
|
+
const gqlDir = join9(projectRoot, "src", "graphql");
|
|
1642
1655
|
await ensureDir(gqlDir, ctx);
|
|
1643
|
-
const schema = await readTextFile(
|
|
1644
|
-
await writeTextFile(
|
|
1645
|
-
const serverDir =
|
|
1656
|
+
const schema = await readTextFile(join9(templatesRoot, "api", "graphql", "schema.graphql"));
|
|
1657
|
+
await writeTextFile(join9(gqlDir, "schema.graphql"), schema, ctx);
|
|
1658
|
+
const serverDir = join9(projectRoot, "src", "server");
|
|
1646
1659
|
await ensureDir(serverDir, ctx);
|
|
1647
1660
|
const serverTemplate = await readTextFile(
|
|
1648
|
-
|
|
1661
|
+
join9(templatesRoot, "api", "graphql", config.frontend.language === "ts" ? "vite-server.ts" : "vite-server.js")
|
|
1649
1662
|
);
|
|
1650
1663
|
await writeTextFile(
|
|
1651
|
-
|
|
1664
|
+
join9(serverDir, config.frontend.language === "ts" ? "index.ts" : "index.js"),
|
|
1652
1665
|
serverTemplate,
|
|
1653
1666
|
ctx
|
|
1654
1667
|
);
|
|
1655
|
-
const clientDir =
|
|
1668
|
+
const clientDir = join9(projectRoot, "src", "graphql");
|
|
1656
1669
|
const client = await readTextFile(
|
|
1657
|
-
|
|
1670
|
+
join9(templatesRoot, "api", "graphql", config.frontend.language === "ts" ? "client.ts" : "client.js")
|
|
1658
1671
|
);
|
|
1659
|
-
await writeTextFile(
|
|
1672
|
+
await writeTextFile(join9(clientDir, config.frontend.language === "ts" ? "client.ts" : "client.js"), client, ctx);
|
|
1660
1673
|
const usage = await readTextFile(
|
|
1661
|
-
|
|
1674
|
+
join9(templatesRoot, "api", "graphql", config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx")
|
|
1662
1675
|
);
|
|
1663
1676
|
await writeTextFile(
|
|
1664
|
-
|
|
1677
|
+
join9(clientDir, config.frontend.language === "ts" ? "client-usage.tsx" : "client-usage.jsx"),
|
|
1665
1678
|
usage,
|
|
1666
1679
|
ctx
|
|
1667
1680
|
);
|
|
@@ -1670,8 +1683,8 @@ async function generateApiFiles(root, config, ctx) {
|
|
|
1670
1683
|
}
|
|
1671
1684
|
|
|
1672
1685
|
// src/ai-agents/config-generator.ts
|
|
1673
|
-
import { join as
|
|
1674
|
-
import { existsSync as
|
|
1686
|
+
import { join as join10 } from "path";
|
|
1687
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1675
1688
|
function buildAgentContext(config) {
|
|
1676
1689
|
return {
|
|
1677
1690
|
stack: {
|
|
@@ -1853,22 +1866,22 @@ function buildHints(config) {
|
|
|
1853
1866
|
async function generateAiAgentConfigs(root, config, ctx) {
|
|
1854
1867
|
if (!config.aiAgents || config.aiAgents.length === 0) return;
|
|
1855
1868
|
const projectRoot = resolveProjectRoot(root, config);
|
|
1856
|
-
const agentsRoot =
|
|
1869
|
+
const agentsRoot = join10(projectRoot, ".ai-agents");
|
|
1857
1870
|
await ensureDir(agentsRoot, ctx);
|
|
1858
|
-
const serversRoot =
|
|
1871
|
+
const serversRoot = join10(agentsRoot, "servers");
|
|
1859
1872
|
await ensureDir(serversRoot, ctx);
|
|
1860
|
-
const protocolsRoot =
|
|
1873
|
+
const protocolsRoot = join10(agentsRoot, "protocols");
|
|
1861
1874
|
await ensureDir(protocolsRoot, ctx);
|
|
1862
1875
|
const tools = buildTools(config);
|
|
1863
1876
|
const hints = buildHints(config);
|
|
1864
1877
|
for (const agent of config.aiAgents) {
|
|
1865
|
-
const agentDir =
|
|
1878
|
+
const agentDir = join10(agentsRoot, agent);
|
|
1866
1879
|
await ensureDir(agentDir, ctx);
|
|
1867
1880
|
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1868
1881
|
const contextJson = JSON.stringify(buildAgentContext(config), null, 2);
|
|
1869
|
-
await writeTextFile(
|
|
1882
|
+
await writeTextFile(join10(agentDir, "context.json"), contextJson + "\n", ctx);
|
|
1870
1883
|
const toolsJson = JSON.stringify({ tools }, null, 2);
|
|
1871
|
-
await writeTextFile(
|
|
1884
|
+
await writeTextFile(join10(agentDir, "tools.json"), toolsJson + "\n", ctx);
|
|
1872
1885
|
if (agent === "claude") {
|
|
1873
1886
|
const content = JSON.stringify(
|
|
1874
1887
|
{
|
|
@@ -1882,19 +1895,19 @@ async function generateAiAgentConfigs(root, config, ctx) {
|
|
|
1882
1895
|
null,
|
|
1883
1896
|
2
|
|
1884
1897
|
);
|
|
1885
|
-
await writeTextFile(
|
|
1886
|
-
const claudeRoot =
|
|
1898
|
+
await writeTextFile(join10(agentDir, "claude_desktop_config.json"), content + "\n", ctx);
|
|
1899
|
+
const claudeRoot = join10(projectRoot, ".claude");
|
|
1887
1900
|
await ensureDir(claudeRoot, ctx);
|
|
1888
|
-
await writeTextFile(
|
|
1901
|
+
await writeTextFile(join10(claudeRoot, "claude_desktop_config.json"), content + "\n", ctx);
|
|
1889
1902
|
await writeTextFile(
|
|
1890
|
-
|
|
1903
|
+
join10(claudeRoot, "README.md"),
|
|
1891
1904
|
"StackForge generated this folder for Claude. Use claude_desktop_config.json to configure the MCP server. The server lives in .ai-agents/servers/claude/mcp-server.(ts|js).\n",
|
|
1892
1905
|
ctx
|
|
1893
1906
|
);
|
|
1894
|
-
const serverDir =
|
|
1907
|
+
const serverDir = join10(serversRoot, "claude");
|
|
1895
1908
|
await ensureDir(serverDir, ctx);
|
|
1896
1909
|
const serverContent = buildMcpServerContent(ext, tools, hints);
|
|
1897
|
-
await writeTextFile(
|
|
1910
|
+
await writeTextFile(join10(serverDir, `mcp-server.${ext}`), serverContent, ctx);
|
|
1898
1911
|
const mcpSpec = JSON.stringify(
|
|
1899
1912
|
{
|
|
1900
1913
|
name: "stackforge",
|
|
@@ -1903,17 +1916,17 @@ async function generateAiAgentConfigs(root, config, ctx) {
|
|
|
1903
1916
|
null,
|
|
1904
1917
|
2
|
|
1905
1918
|
);
|
|
1906
|
-
await writeTextFile(
|
|
1919
|
+
await writeTextFile(join10(serverDir, "mcp.json"), mcpSpec + "\n", ctx);
|
|
1907
1920
|
await writeTextFile(
|
|
1908
|
-
|
|
1921
|
+
join10(serverDir, "README.md"),
|
|
1909
1922
|
"Run the MCP server: `node mcp-server.js` (or .ts with tsx). Endpoints: /tools and /invoke.\n",
|
|
1910
1923
|
ctx
|
|
1911
1924
|
);
|
|
1912
1925
|
}
|
|
1913
1926
|
if (agent === "copilot") {
|
|
1914
1927
|
const content = JSON.stringify({ functions: buildFunctionDefinitions(tools) }, null, 2);
|
|
1915
|
-
await writeTextFile(
|
|
1916
|
-
const serverDir =
|
|
1928
|
+
await writeTextFile(join10(agentDir, "functions.json"), content + "\n", ctx);
|
|
1929
|
+
const serverDir = join10(serversRoot, "copilot");
|
|
1917
1930
|
await ensureDir(serverDir, ctx);
|
|
1918
1931
|
const serverContent = `export const functions = ${JSON.stringify(buildFunctionDefinitions(tools), null, 2)};
|
|
1919
1932
|
|
|
@@ -1921,21 +1934,21 @@ export function handleFunctionCall(name, args) {
|
|
|
1921
1934
|
return { name, args, ok: true, message: 'Implement tool logic here.' };
|
|
1922
1935
|
}
|
|
1923
1936
|
`;
|
|
1924
|
-
await writeTextFile(
|
|
1925
|
-
await writeTextFile(
|
|
1937
|
+
await writeTextFile(join10(serverDir, `functions.${ext}`), serverContent, ctx);
|
|
1938
|
+
await writeTextFile(join10(serverDir, "functions.json"), content + "\n", ctx);
|
|
1926
1939
|
}
|
|
1927
1940
|
if (agent === "codex") {
|
|
1928
1941
|
const content = JSON.stringify({ functions: buildFunctionDefinitions(tools) }, null, 2);
|
|
1929
|
-
await writeTextFile(
|
|
1930
|
-
const codexRoot =
|
|
1942
|
+
await writeTextFile(join10(agentDir, "functions.json"), content + "\n", ctx);
|
|
1943
|
+
const codexRoot = join10(projectRoot, ".codex");
|
|
1931
1944
|
await ensureDir(codexRoot, ctx);
|
|
1932
|
-
await writeTextFile(
|
|
1945
|
+
await writeTextFile(join10(codexRoot, "functions.json"), content + "\n", ctx);
|
|
1933
1946
|
await writeTextFile(
|
|
1934
|
-
|
|
1947
|
+
join10(codexRoot, "README.md"),
|
|
1935
1948
|
"StackForge generated this folder for Codex. Use functions.json for OpenAI function calling integrations.\n",
|
|
1936
1949
|
ctx
|
|
1937
1950
|
);
|
|
1938
|
-
const serverDir =
|
|
1951
|
+
const serverDir = join10(serversRoot, "codex");
|
|
1939
1952
|
await ensureDir(serverDir, ctx);
|
|
1940
1953
|
const serverContent = `export const functions = ${JSON.stringify(buildFunctionDefinitions(tools), null, 2)};
|
|
1941
1954
|
|
|
@@ -1943,13 +1956,13 @@ export function handleFunctionCall(name, args) {
|
|
|
1943
1956
|
return { name, args, ok: true, message: 'Implement tool logic here.' };
|
|
1944
1957
|
}
|
|
1945
1958
|
`;
|
|
1946
|
-
await writeTextFile(
|
|
1947
|
-
await writeTextFile(
|
|
1959
|
+
await writeTextFile(join10(serverDir, `functions.${ext}`), serverContent, ctx);
|
|
1960
|
+
await writeTextFile(join10(serverDir, "functions.json"), content + "\n", ctx);
|
|
1948
1961
|
}
|
|
1949
1962
|
if (agent === "gemini") {
|
|
1950
1963
|
const content = JSON.stringify({ functions: buildFunctionDefinitions(tools) }, null, 2);
|
|
1951
|
-
await writeTextFile(
|
|
1952
|
-
const serverDir =
|
|
1964
|
+
await writeTextFile(join10(agentDir, "function_declarations.json"), content + "\n", ctx);
|
|
1965
|
+
const serverDir = join10(serversRoot, "gemini");
|
|
1953
1966
|
await ensureDir(serverDir, ctx);
|
|
1954
1967
|
const serverContent = `export const functions = ${JSON.stringify(buildFunctionDefinitions(tools), null, 2)};
|
|
1955
1968
|
|
|
@@ -1957,46 +1970,46 @@ export function handleFunctionCall(name, args) {
|
|
|
1957
1970
|
return { name, args, ok: true, message: 'Implement tool logic here.' };
|
|
1958
1971
|
}
|
|
1959
1972
|
`;
|
|
1960
|
-
await writeTextFile(
|
|
1961
|
-
await writeTextFile(
|
|
1973
|
+
await writeTextFile(join10(serverDir, `functions.${ext}`), serverContent, ctx);
|
|
1974
|
+
await writeTextFile(join10(serverDir, "function_declarations.json"), content + "\n", ctx);
|
|
1962
1975
|
}
|
|
1963
1976
|
if (agent === "cursor") {
|
|
1964
1977
|
const content = `# Cursor rules
|
|
1965
1978
|
# See context.json for project stack details
|
|
1966
1979
|
`;
|
|
1967
|
-
await writeTextFile(
|
|
1968
|
-
await writeTextFile(
|
|
1969
|
-
const cursorRoot =
|
|
1980
|
+
await writeTextFile(join10(agentDir, ".cursorrules"), content, ctx);
|
|
1981
|
+
await writeTextFile(join10(projectRoot, ".cursorrules"), content, ctx);
|
|
1982
|
+
const cursorRoot = join10(projectRoot, ".cursor");
|
|
1970
1983
|
await ensureDir(cursorRoot, ctx);
|
|
1971
1984
|
const extensions = JSON.stringify({ recommendations: ["cursor.cursor"] }, null, 2);
|
|
1972
|
-
await writeTextFile(
|
|
1973
|
-
const serverDir =
|
|
1985
|
+
await writeTextFile(join10(cursorRoot, "extensions.json"), extensions + "\n", ctx);
|
|
1986
|
+
const serverDir = join10(serversRoot, "cursor");
|
|
1974
1987
|
await ensureDir(serverDir, ctx);
|
|
1975
1988
|
const serverContent = `# Cursor uses .cursorrules for guidance.
|
|
1976
1989
|
`;
|
|
1977
|
-
await writeTextFile(
|
|
1990
|
+
await writeTextFile(join10(serverDir, "README.md"), serverContent, ctx);
|
|
1978
1991
|
}
|
|
1979
1992
|
if (agent === "codeium") {
|
|
1980
1993
|
const content = JSON.stringify({ protocol: "lsp", tools, hints }, null, 2);
|
|
1981
|
-
await writeTextFile(
|
|
1994
|
+
await writeTextFile(join10(agentDir, "server-config.json"), content + "\n", ctx);
|
|
1982
1995
|
}
|
|
1983
1996
|
if (agent === "windsurf") {
|
|
1984
|
-
const windsurfRoot =
|
|
1997
|
+
const windsurfRoot = join10(projectRoot, ".windsurf");
|
|
1985
1998
|
await ensureDir(windsurfRoot, ctx);
|
|
1986
1999
|
const content = JSON.stringify({ protocol: "cascade", tools, hints }, null, 2);
|
|
1987
|
-
await writeTextFile(
|
|
2000
|
+
await writeTextFile(join10(windsurfRoot, "cascade.json"), content + "\n", ctx);
|
|
1988
2001
|
}
|
|
1989
2002
|
if (agent === "tabnine") {
|
|
1990
|
-
const tabnineRoot =
|
|
2003
|
+
const tabnineRoot = join10(projectRoot, ".tabnine");
|
|
1991
2004
|
await ensureDir(tabnineRoot, ctx);
|
|
1992
2005
|
const content = JSON.stringify({ tools, hints }, null, 2);
|
|
1993
|
-
await writeTextFile(
|
|
2006
|
+
await writeTextFile(join10(tabnineRoot, "config.json"), content + "\n", ctx);
|
|
1994
2007
|
}
|
|
1995
2008
|
}
|
|
1996
2009
|
const protocolTools = tools;
|
|
1997
2010
|
const openAiFunctions = buildFunctionDefinitions(protocolTools);
|
|
1998
2011
|
const openAiSchema = JSON.stringify({ functions: openAiFunctions }, null, 2);
|
|
1999
|
-
await writeTextFile(
|
|
2012
|
+
await writeTextFile(join10(protocolsRoot, "openai-functions.json"), openAiSchema + "\n", ctx);
|
|
2000
2013
|
const lspSchema = JSON.stringify(
|
|
2001
2014
|
{
|
|
2002
2015
|
version: "0.1",
|
|
@@ -2007,16 +2020,16 @@ export function handleFunctionCall(name, args) {
|
|
|
2007
2020
|
null,
|
|
2008
2021
|
2
|
|
2009
2022
|
);
|
|
2010
|
-
await writeTextFile(
|
|
2011
|
-
const docsDir =
|
|
2023
|
+
await writeTextFile(join10(protocolsRoot, "lsp.json"), lspSchema + "\n", ctx);
|
|
2024
|
+
const docsDir = join10(projectRoot, "docs");
|
|
2012
2025
|
await ensureDir(docsDir, ctx);
|
|
2013
2026
|
const aiDoc = buildAiAgentsDoc(config);
|
|
2014
|
-
await writeTextFile(
|
|
2027
|
+
await writeTextFile(join10(docsDir, "AI_AGENTS.md"), aiDoc + "\n", ctx);
|
|
2015
2028
|
}
|
|
2016
2029
|
function resolveProjectRoot(root, config) {
|
|
2017
|
-
const candidate =
|
|
2018
|
-
if (
|
|
2019
|
-
return
|
|
2030
|
+
const candidate = join10(root, "stackforge.json");
|
|
2031
|
+
if (existsSync3(candidate)) return root;
|
|
2032
|
+
return join10(root, config.projectName);
|
|
2020
2033
|
}
|
|
2021
2034
|
function buildAiAgentsDoc(config) {
|
|
2022
2035
|
const agentsList = config.aiAgents.map((agent) => `- ${agent}`).join("\n");
|
|
@@ -2126,73 +2139,72 @@ server.listen(port, () => {
|
|
|
2126
2139
|
}
|
|
2127
2140
|
|
|
2128
2141
|
// src/generators/features/feature-files.ts
|
|
2129
|
-
import { join as
|
|
2130
|
-
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
2142
|
+
import { join as join11 } from "path";
|
|
2131
2143
|
async function generateFeatureFiles(root, config, ctx) {
|
|
2132
|
-
const projectRoot =
|
|
2133
|
-
const templatesRoot =
|
|
2134
|
-
const libDir =
|
|
2144
|
+
const projectRoot = join11(root, config.projectName);
|
|
2145
|
+
const templatesRoot = TEMPLATES_DIR;
|
|
2146
|
+
const libDir = join11(projectRoot, "src", "lib");
|
|
2135
2147
|
const isTs = config.frontend.language === "ts";
|
|
2136
2148
|
if (config.features.includes("email")) {
|
|
2137
|
-
const dir =
|
|
2149
|
+
const dir = join11(projectRoot, "features", "email");
|
|
2138
2150
|
await ensureDir(dir, ctx);
|
|
2139
2151
|
await ensureDir(libDir, ctx);
|
|
2140
|
-
const readme = await readTextFile(
|
|
2152
|
+
const readme = await readTextFile(join11(templatesRoot, "features", "email", "README.md"));
|
|
2141
2153
|
const resendClient = await readTextFile(
|
|
2142
|
-
|
|
2154
|
+
join11(templatesRoot, "features", "email", isTs ? "resend.ts" : "resend.js")
|
|
2143
2155
|
);
|
|
2144
|
-
await writeTextFile(
|
|
2145
|
-
await writeTextFile(
|
|
2146
|
-
await appendEnvLine(
|
|
2156
|
+
await writeTextFile(join11(dir, "README.md"), readme, ctx);
|
|
2157
|
+
await writeTextFile(join11(libDir, isTs ? "resend.ts" : "resend.js"), resendClient, ctx);
|
|
2158
|
+
await appendEnvLine(join11(projectRoot, ".env.example"), 'RESEND_API_KEY=""', ctx);
|
|
2147
2159
|
}
|
|
2148
2160
|
if (config.features.includes("storage")) {
|
|
2149
|
-
const dir =
|
|
2161
|
+
const dir = join11(projectRoot, "features", "storage");
|
|
2150
2162
|
await ensureDir(dir, ctx);
|
|
2151
2163
|
await ensureDir(libDir, ctx);
|
|
2152
|
-
const readme = await readTextFile(
|
|
2153
|
-
await writeTextFile(
|
|
2164
|
+
const readme = await readTextFile(join11(templatesRoot, "features", "storage", "README.md"));
|
|
2165
|
+
await writeTextFile(join11(dir, "README.md"), readme, ctx);
|
|
2154
2166
|
const storageClient = await readTextFile(
|
|
2155
|
-
|
|
2167
|
+
join11(templatesRoot, "features", "storage", isTs ? "storage.ts" : "storage.js")
|
|
2156
2168
|
);
|
|
2157
|
-
await writeTextFile(
|
|
2158
|
-
await appendEnvLine(
|
|
2169
|
+
await writeTextFile(join11(libDir, isTs ? "storage.ts" : "storage.js"), storageClient, ctx);
|
|
2170
|
+
await appendEnvLine(join11(projectRoot, ".env.example"), 'CLOUDINARY_URL=""', ctx);
|
|
2159
2171
|
}
|
|
2160
2172
|
if (config.features.includes("payments")) {
|
|
2161
|
-
const dir =
|
|
2173
|
+
const dir = join11(projectRoot, "features", "payments");
|
|
2162
2174
|
await ensureDir(dir, ctx);
|
|
2163
2175
|
await ensureDir(libDir, ctx);
|
|
2164
|
-
const readme = await readTextFile(
|
|
2176
|
+
const readme = await readTextFile(join11(templatesRoot, "features", "payments", "README.md"));
|
|
2165
2177
|
const stripeClient = await readTextFile(
|
|
2166
|
-
|
|
2178
|
+
join11(templatesRoot, "features", "payments", isTs ? "stripe.ts" : "stripe.js")
|
|
2167
2179
|
);
|
|
2168
|
-
await writeTextFile(
|
|
2169
|
-
await writeTextFile(
|
|
2170
|
-
await appendEnvLine(
|
|
2180
|
+
await writeTextFile(join11(dir, "README.md"), readme, ctx);
|
|
2181
|
+
await writeTextFile(join11(libDir, isTs ? "stripe.ts" : "stripe.js"), stripeClient, ctx);
|
|
2182
|
+
await appendEnvLine(join11(projectRoot, ".env.example"), 'STRIPE_SECRET_KEY=""', ctx);
|
|
2171
2183
|
}
|
|
2172
2184
|
if (config.features.includes("analytics")) {
|
|
2173
|
-
const dir =
|
|
2185
|
+
const dir = join11(projectRoot, "features", "analytics");
|
|
2174
2186
|
await ensureDir(dir, ctx);
|
|
2175
2187
|
await ensureDir(libDir, ctx);
|
|
2176
|
-
const readme = await readTextFile(
|
|
2188
|
+
const readme = await readTextFile(join11(templatesRoot, "features", "analytics", "README.md"));
|
|
2177
2189
|
const client = await readTextFile(
|
|
2178
|
-
|
|
2190
|
+
join11(templatesRoot, "features", "analytics", isTs ? "posthog.ts" : "posthog.js")
|
|
2179
2191
|
);
|
|
2180
|
-
await writeTextFile(
|
|
2181
|
-
await writeTextFile(
|
|
2182
|
-
await appendEnvLine(
|
|
2183
|
-
await appendEnvLine(
|
|
2192
|
+
await writeTextFile(join11(dir, "README.md"), readme, ctx);
|
|
2193
|
+
await writeTextFile(join11(libDir, isTs ? "posthog.ts" : "posthog.js"), client, ctx);
|
|
2194
|
+
await appendEnvLine(join11(projectRoot, ".env.example"), 'NEXT_PUBLIC_POSTHOG_KEY=""', ctx);
|
|
2195
|
+
await appendEnvLine(join11(projectRoot, ".env.example"), 'NEXT_PUBLIC_POSTHOG_HOST=""', ctx);
|
|
2184
2196
|
}
|
|
2185
2197
|
if (config.features.includes("error-tracking")) {
|
|
2186
|
-
const dir =
|
|
2198
|
+
const dir = join11(projectRoot, "features", "error-tracking");
|
|
2187
2199
|
await ensureDir(dir, ctx);
|
|
2188
2200
|
await ensureDir(libDir, ctx);
|
|
2189
|
-
const readme = await readTextFile(
|
|
2201
|
+
const readme = await readTextFile(join11(templatesRoot, "features", "error-tracking", "README.md"));
|
|
2190
2202
|
const client = await readTextFile(
|
|
2191
|
-
|
|
2203
|
+
join11(templatesRoot, "features", "error-tracking", isTs ? "sentry.ts" : "sentry.js")
|
|
2192
2204
|
);
|
|
2193
|
-
await writeTextFile(
|
|
2194
|
-
await writeTextFile(
|
|
2195
|
-
await appendEnvLine(
|
|
2205
|
+
await writeTextFile(join11(dir, "README.md"), readme, ctx);
|
|
2206
|
+
await writeTextFile(join11(libDir, isTs ? "sentry.ts" : "sentry.js"), client, ctx);
|
|
2207
|
+
await appendEnvLine(join11(projectRoot, ".env.example"), 'SENTRY_DSN=""', ctx);
|
|
2196
2208
|
}
|
|
2197
2209
|
}
|
|
2198
2210
|
|
|
@@ -2329,7 +2341,7 @@ function runInstall(pm, cwd) {
|
|
|
2329
2341
|
}
|
|
2330
2342
|
|
|
2331
2343
|
// src/cli/commands/create.ts
|
|
2332
|
-
import { join as
|
|
2344
|
+
import { join as join12, resolve } from "path";
|
|
2333
2345
|
function parseCsv(input) {
|
|
2334
2346
|
if (!input) return void 0;
|
|
2335
2347
|
return input.split(",").map((a) => a.trim()).filter(Boolean);
|
|
@@ -2354,7 +2366,7 @@ var createCommand = new Command("create").argument("[project-name]", "name of th
|
|
|
2354
2366
|
const outDir = options.outDir ? resolve(options.outDir) : process.cwd();
|
|
2355
2367
|
await runGenerators(outDir, config, ctx);
|
|
2356
2368
|
if (options.install !== false && !options.dryRun) {
|
|
2357
|
-
const projectRoot =
|
|
2369
|
+
const projectRoot = join12(outDir, config.projectName);
|
|
2358
2370
|
logger.info("Installing dependencies...");
|
|
2359
2371
|
await runInstall(config.packageManager, projectRoot);
|
|
2360
2372
|
}
|
|
@@ -2532,7 +2544,7 @@ async function syncPackageJson(path, oldConfig, newConfig) {
|
|
|
2532
2544
|
}
|
|
2533
2545
|
|
|
2534
2546
|
// src/cli/commands/add.ts
|
|
2535
|
-
import { dirname as
|
|
2547
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
2536
2548
|
function parseFeature(feature) {
|
|
2537
2549
|
const parts = feature.split(":");
|
|
2538
2550
|
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
@@ -2552,14 +2564,14 @@ var addCommand = new Command3("add").argument("<feature>", "feature to add (cate
|
|
|
2552
2564
|
validateDependencies(next);
|
|
2553
2565
|
await writeProjectConfig(cwd, next);
|
|
2554
2566
|
await syncPackageJson(`${cwd}/package.json`, current, next);
|
|
2555
|
-
const root =
|
|
2567
|
+
const root = dirname4(cwd);
|
|
2556
2568
|
if (category === "ui") await generateUiFiles(root, next);
|
|
2557
2569
|
if (category === "database" || category === "orm") await generateDatabaseFiles(root, next);
|
|
2558
2570
|
if (category === "auth") await generateAuthFiles(root, next);
|
|
2559
2571
|
if (category === "api") await generateApiFiles(root, next);
|
|
2560
2572
|
if (category === "feature") await generateFeatureFiles(root, next);
|
|
2561
2573
|
const readme = buildProjectReadme(next);
|
|
2562
|
-
await writeTextFile(
|
|
2574
|
+
await writeTextFile(join13(cwd, "README.md"), readme + "\n");
|
|
2563
2575
|
logger.info(`Added ${feature}`);
|
|
2564
2576
|
} catch (err) {
|
|
2565
2577
|
logger.error(err instanceof Error ? err.message : String(err));
|
|
@@ -2571,188 +2583,188 @@ var addCommand = new Command3("add").argument("<feature>", "feature to add (cate
|
|
|
2571
2583
|
import { Command as Command4 } from "commander";
|
|
2572
2584
|
|
|
2573
2585
|
// src/utils/feature-cleanup.ts
|
|
2574
|
-
import { join as
|
|
2586
|
+
import { join as join14 } from "path";
|
|
2575
2587
|
async function cleanupFeature(cwd, config, category, value) {
|
|
2576
2588
|
const root = cwd;
|
|
2577
2589
|
if (category === "ui") {
|
|
2578
2590
|
if (config.ui.library === "tailwind") {
|
|
2579
|
-
await removePath(
|
|
2580
|
-
await removePath(
|
|
2581
|
-
await removePath(
|
|
2582
|
-
await removePath(
|
|
2583
|
-
await removePath(
|
|
2591
|
+
await removePath(join14(root, "tailwind.config.js"));
|
|
2592
|
+
await removePath(join14(root, "postcss.config.js"));
|
|
2593
|
+
await removePath(join14(root, "src", "styles.css"));
|
|
2594
|
+
await removePath(join14(root, "src", "components", "ui-demo.tsx"));
|
|
2595
|
+
await removePath(join14(root, "src", "components", "ui-demo.jsx"));
|
|
2584
2596
|
}
|
|
2585
2597
|
if (config.ui.library === "shadcn") {
|
|
2586
|
-
await removePath(
|
|
2587
|
-
await removePath(
|
|
2588
|
-
await removePath(
|
|
2589
|
-
await removePath(
|
|
2590
|
-
await removePath(
|
|
2591
|
-
await removePath(
|
|
2592
|
-
await removePath(
|
|
2598
|
+
await removePath(join14(root, "components"));
|
|
2599
|
+
await removePath(join14(root, "components.json"));
|
|
2600
|
+
await removePath(join14(root, "src", "lib", "utils.ts"));
|
|
2601
|
+
await removePath(join14(root, "src", "lib", "utils.js"));
|
|
2602
|
+
await removePath(join14(root, "src", "components", "ui"));
|
|
2603
|
+
await removePath(join14(root, "src", "components", "ui-demo.tsx"));
|
|
2604
|
+
await removePath(join14(root, "src", "components", "ui-demo.jsx"));
|
|
2593
2605
|
}
|
|
2594
2606
|
if (config.ui.library === "mui" || config.ui.library === "chakra" || config.ui.library === "mantine" || config.ui.library === "antd" || config.ui.library === "nextui") {
|
|
2595
|
-
await removePath(
|
|
2596
|
-
await removePath(
|
|
2597
|
-
await removePath(
|
|
2598
|
-
await removePath(
|
|
2599
|
-
await removePath(
|
|
2607
|
+
await removePath(join14(root, "components"));
|
|
2608
|
+
await removePath(join14(root, "src", "theme.ts"));
|
|
2609
|
+
await removePath(join14(root, "src", "theme.js"));
|
|
2610
|
+
await removePath(join14(root, "src", "components", "ui-demo.tsx"));
|
|
2611
|
+
await removePath(join14(root, "src", "components", "ui-demo.jsx"));
|
|
2600
2612
|
}
|
|
2601
2613
|
}
|
|
2602
2614
|
if (category === "auth") {
|
|
2603
2615
|
if (config.auth.provider === "nextauth") {
|
|
2604
|
-
await removePath(
|
|
2605
|
-
await removePath(
|
|
2606
|
-
await removePath(
|
|
2607
|
-
await removeEnvKey(
|
|
2608
|
-
await removeEnvKey(
|
|
2616
|
+
await removePath(join14(root, "app", "api", "auth"));
|
|
2617
|
+
await removePath(join14(root, "app", "auth", "protected"));
|
|
2618
|
+
await removePath(join14(root, "app", "auth", "signin"));
|
|
2619
|
+
await removeEnvKey(join14(root, ".env.example"), "NEXTAUTH_SECRET");
|
|
2620
|
+
await removeEnvKey(join14(root, ".env.example"), "NEXTAUTH_URL");
|
|
2609
2621
|
}
|
|
2610
2622
|
if (config.auth.provider === "clerk") {
|
|
2611
|
-
await removePath(
|
|
2612
|
-
await removePath(
|
|
2613
|
-
await removePath(
|
|
2614
|
-
await removePath(
|
|
2615
|
-
await removePath(
|
|
2616
|
-
await removePath(
|
|
2617
|
-
await removeEnvKey(
|
|
2618
|
-
await removeEnvKey(
|
|
2623
|
+
await removePath(join14(root, "middleware.ts"));
|
|
2624
|
+
await removePath(join14(root, "middleware.js"));
|
|
2625
|
+
await removePath(join14(root, "src", "lib", "clerk.ts"));
|
|
2626
|
+
await removePath(join14(root, "src", "lib", "clerk.js"));
|
|
2627
|
+
await removePath(join14(root, "app", "auth", "protected"));
|
|
2628
|
+
await removePath(join14(root, "app", "auth", "signin"));
|
|
2629
|
+
await removeEnvKey(join14(root, ".env.example"), "CLERK_SECRET_KEY");
|
|
2630
|
+
await removeEnvKey(join14(root, ".env.example"), "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY");
|
|
2619
2631
|
}
|
|
2620
2632
|
if (config.auth.provider === "supabase") {
|
|
2621
|
-
await removePath(
|
|
2622
|
-
await removePath(
|
|
2623
|
-
await removePath(
|
|
2624
|
-
await removePath(
|
|
2625
|
-
await removePath(
|
|
2626
|
-
await removePath(
|
|
2627
|
-
await removePath(
|
|
2628
|
-
await removePath(
|
|
2629
|
-
await removeEnvKey(
|
|
2630
|
-
await removeEnvKey(
|
|
2631
|
-
}
|
|
2632
|
-
await removePath(
|
|
2633
|
+
await removePath(join14(root, "src", "lib", "supabase.ts"));
|
|
2634
|
+
await removePath(join14(root, "src", "lib", "supabase.js"));
|
|
2635
|
+
await removePath(join14(root, "src", "lib", "supabase-server.ts"));
|
|
2636
|
+
await removePath(join14(root, "src", "lib", "supabase-server.js"));
|
|
2637
|
+
await removePath(join14(root, "app", "auth", "protected"));
|
|
2638
|
+
await removePath(join14(root, "app", "auth", "signin"));
|
|
2639
|
+
await removePath(join14(root, "src", "auth", "signin.tsx"));
|
|
2640
|
+
await removePath(join14(root, "src", "auth", "signin.jsx"));
|
|
2641
|
+
await removeEnvKey(join14(root, ".env.example"), "NEXT_PUBLIC_SUPABASE_URL");
|
|
2642
|
+
await removeEnvKey(join14(root, ".env.example"), "NEXT_PUBLIC_SUPABASE_ANON_KEY");
|
|
2643
|
+
}
|
|
2644
|
+
await removePath(join14(root, "auth"));
|
|
2633
2645
|
}
|
|
2634
2646
|
if (category === "api") {
|
|
2635
2647
|
if (config.api.type === "rest") {
|
|
2636
|
-
await removePath(
|
|
2637
|
-
await removePath(
|
|
2638
|
-
await removePath(
|
|
2639
|
-
await removePath(
|
|
2640
|
-
await removePath(
|
|
2641
|
-
await removePath(
|
|
2642
|
-
await removePath(
|
|
2648
|
+
await removePath(join14(root, "app", "api", "hello"));
|
|
2649
|
+
await removePath(join14(root, "app", "api", "users"));
|
|
2650
|
+
await removePath(join14(root, "app", "examples"));
|
|
2651
|
+
await removePath(join14(root, "src", "server"));
|
|
2652
|
+
await removePath(join14(root, "src", "api"));
|
|
2653
|
+
await removePath(join14(root, "src", "api", "client-usage.tsx"));
|
|
2654
|
+
await removePath(join14(root, "src", "api", "client-usage.jsx"));
|
|
2643
2655
|
}
|
|
2644
2656
|
if (config.api.type === "trpc") {
|
|
2645
|
-
await removePath(
|
|
2646
|
-
await removePath(
|
|
2647
|
-
await removePath(
|
|
2648
|
-
await removePath(
|
|
2649
|
-
await removePath(
|
|
2650
|
-
await removePath(
|
|
2651
|
-
await removePath(
|
|
2657
|
+
await removePath(join14(root, "app", "api", "trpc"));
|
|
2658
|
+
await removePath(join14(root, "app", "examples"));
|
|
2659
|
+
await removePath(join14(root, "src", "server", "api"));
|
|
2660
|
+
await removePath(join14(root, "src", "trpc"));
|
|
2661
|
+
await removePath(join14(root, "src", "trpc", "client-usage.tsx"));
|
|
2662
|
+
await removePath(join14(root, "src", "trpc", "client-usage.jsx"));
|
|
2663
|
+
await removePath(join14(root, "src", "server"));
|
|
2652
2664
|
}
|
|
2653
2665
|
if (config.api.type === "graphql") {
|
|
2654
|
-
await removePath(
|
|
2655
|
-
await removePath(
|
|
2656
|
-
await removePath(
|
|
2657
|
-
await removePath(
|
|
2658
|
-
await removePath(
|
|
2659
|
-
await removePath(
|
|
2666
|
+
await removePath(join14(root, "app", "api", "graphql"));
|
|
2667
|
+
await removePath(join14(root, "app", "examples"));
|
|
2668
|
+
await removePath(join14(root, "src", "graphql"));
|
|
2669
|
+
await removePath(join14(root, "src", "server"));
|
|
2670
|
+
await removePath(join14(root, "src", "graphql", "client-usage.tsx"));
|
|
2671
|
+
await removePath(join14(root, "src", "graphql", "client-usage.jsx"));
|
|
2660
2672
|
}
|
|
2661
|
-
await removePath(
|
|
2673
|
+
await removePath(join14(root, "api"));
|
|
2662
2674
|
}
|
|
2663
2675
|
if (category === "database") {
|
|
2664
2676
|
if (config.database.orm === "drizzle") {
|
|
2665
|
-
await removePath(
|
|
2666
|
-
await removePath(
|
|
2677
|
+
await removePath(join14(root, "drizzle.config.ts"));
|
|
2678
|
+
await removePath(join14(root, "drizzle"));
|
|
2667
2679
|
}
|
|
2668
2680
|
if (config.database.orm === "prisma") {
|
|
2669
|
-
await removePath(
|
|
2670
|
-
await removePath(
|
|
2671
|
-
await removePath(
|
|
2672
|
-
await removePath(
|
|
2673
|
-
await removePath(
|
|
2681
|
+
await removePath(join14(root, "prisma"));
|
|
2682
|
+
await removePath(join14(root, "src", "db", "prisma.ts"));
|
|
2683
|
+
await removePath(join14(root, "src", "db", "prisma.js"));
|
|
2684
|
+
await removePath(join14(root, "src", "db", "prisma-example.ts"));
|
|
2685
|
+
await removePath(join14(root, "src", "db", "prisma-example.js"));
|
|
2674
2686
|
}
|
|
2675
2687
|
if (config.database.orm === "mongoose") {
|
|
2676
|
-
await removePath(
|
|
2677
|
-
await removePath(
|
|
2678
|
-
await removePath(
|
|
2679
|
-
await removePath(
|
|
2688
|
+
await removePath(join14(root, "src", "db", "mongoose.ts"));
|
|
2689
|
+
await removePath(join14(root, "src", "db", "mongoose.js"));
|
|
2690
|
+
await removePath(join14(root, "src", "db", "mongoose-model.ts"));
|
|
2691
|
+
await removePath(join14(root, "src", "db", "mongoose-model.js"));
|
|
2680
2692
|
}
|
|
2681
2693
|
if (config.database.orm === "typeorm") {
|
|
2682
|
-
await removePath(
|
|
2683
|
-
await removePath(
|
|
2684
|
-
await removePath(
|
|
2685
|
-
await removePath(
|
|
2694
|
+
await removePath(join14(root, "src", "db", "data-source.ts"));
|
|
2695
|
+
await removePath(join14(root, "src", "db", "data-source.js"));
|
|
2696
|
+
await removePath(join14(root, "src", "db", "entities"));
|
|
2697
|
+
await removePath(join14(root, "src", "db", "migrations"));
|
|
2686
2698
|
}
|
|
2687
|
-
await removeEnvKey(
|
|
2688
|
-
await removeEnvKey(
|
|
2689
|
-
await removeEnvKey(
|
|
2690
|
-
await removeEnvKey(
|
|
2691
|
-
await removeEnvKey(
|
|
2699
|
+
await removeEnvKey(join14(root, ".env.example"), "DATABASE_URL");
|
|
2700
|
+
await removeEnvKey(join14(root, ".env.example"), "NEON_API_KEY");
|
|
2701
|
+
await removeEnvKey(join14(root, ".env.example"), "NEON_PROJECT_ID");
|
|
2702
|
+
await removeEnvKey(join14(root, ".env.example"), "SUPABASE_URL");
|
|
2703
|
+
await removeEnvKey(join14(root, ".env.example"), "SUPABASE_ANON_KEY");
|
|
2692
2704
|
}
|
|
2693
2705
|
if (category === "orm") {
|
|
2694
2706
|
if (config.database.orm === "drizzle") {
|
|
2695
|
-
await removePath(
|
|
2696
|
-
await removePath(
|
|
2707
|
+
await removePath(join14(root, "drizzle.config.ts"));
|
|
2708
|
+
await removePath(join14(root, "drizzle"));
|
|
2697
2709
|
}
|
|
2698
2710
|
if (config.database.orm === "prisma") {
|
|
2699
|
-
await removePath(
|
|
2700
|
-
await removePath(
|
|
2701
|
-
await removePath(
|
|
2702
|
-
await removePath(
|
|
2703
|
-
await removePath(
|
|
2711
|
+
await removePath(join14(root, "prisma"));
|
|
2712
|
+
await removePath(join14(root, "src", "db", "prisma.ts"));
|
|
2713
|
+
await removePath(join14(root, "src", "db", "prisma.js"));
|
|
2714
|
+
await removePath(join14(root, "src", "db", "prisma-example.ts"));
|
|
2715
|
+
await removePath(join14(root, "src", "db", "prisma-example.js"));
|
|
2704
2716
|
}
|
|
2705
2717
|
if (config.database.orm === "mongoose") {
|
|
2706
|
-
await removePath(
|
|
2707
|
-
await removePath(
|
|
2708
|
-
await removePath(
|
|
2709
|
-
await removePath(
|
|
2718
|
+
await removePath(join14(root, "src", "db", "mongoose.ts"));
|
|
2719
|
+
await removePath(join14(root, "src", "db", "mongoose.js"));
|
|
2720
|
+
await removePath(join14(root, "src", "db", "mongoose-model.ts"));
|
|
2721
|
+
await removePath(join14(root, "src", "db", "mongoose-model.js"));
|
|
2710
2722
|
}
|
|
2711
2723
|
if (config.database.orm === "typeorm") {
|
|
2712
|
-
await removePath(
|
|
2713
|
-
await removePath(
|
|
2714
|
-
await removePath(
|
|
2715
|
-
await removePath(
|
|
2724
|
+
await removePath(join14(root, "src", "db", "data-source.ts"));
|
|
2725
|
+
await removePath(join14(root, "src", "db", "data-source.js"));
|
|
2726
|
+
await removePath(join14(root, "src", "db", "entities"));
|
|
2727
|
+
await removePath(join14(root, "src", "db", "migrations"));
|
|
2716
2728
|
}
|
|
2717
2729
|
}
|
|
2718
2730
|
if (category === "feature") {
|
|
2719
2731
|
const targets = value ? [value] : config.features;
|
|
2720
2732
|
if (targets.includes("email")) {
|
|
2721
|
-
await removePath(
|
|
2722
|
-
await removeEnvKey(
|
|
2723
|
-
await removePath(
|
|
2724
|
-
await removePath(
|
|
2733
|
+
await removePath(join14(root, "features", "email"));
|
|
2734
|
+
await removeEnvKey(join14(root, ".env.example"), "RESEND_API_KEY");
|
|
2735
|
+
await removePath(join14(root, "src", "lib", "resend.ts"));
|
|
2736
|
+
await removePath(join14(root, "src", "lib", "resend.js"));
|
|
2725
2737
|
}
|
|
2726
2738
|
if (targets.includes("storage")) {
|
|
2727
|
-
await removePath(
|
|
2728
|
-
await removeEnvKey(
|
|
2729
|
-
await removePath(
|
|
2730
|
-
await removePath(
|
|
2739
|
+
await removePath(join14(root, "features", "storage"));
|
|
2740
|
+
await removeEnvKey(join14(root, ".env.example"), "CLOUDINARY_URL");
|
|
2741
|
+
await removePath(join14(root, "src", "lib", "storage.ts"));
|
|
2742
|
+
await removePath(join14(root, "src", "lib", "storage.js"));
|
|
2731
2743
|
}
|
|
2732
2744
|
if (targets.includes("payments")) {
|
|
2733
|
-
await removePath(
|
|
2734
|
-
await removeEnvKey(
|
|
2735
|
-
await removePath(
|
|
2736
|
-
await removePath(
|
|
2745
|
+
await removePath(join14(root, "features", "payments"));
|
|
2746
|
+
await removeEnvKey(join14(root, ".env.example"), "STRIPE_SECRET_KEY");
|
|
2747
|
+
await removePath(join14(root, "src", "lib", "stripe.ts"));
|
|
2748
|
+
await removePath(join14(root, "src", "lib", "stripe.js"));
|
|
2737
2749
|
}
|
|
2738
2750
|
if (targets.includes("analytics")) {
|
|
2739
|
-
await removePath(
|
|
2740
|
-
await removeEnvKey(
|
|
2741
|
-
await removeEnvKey(
|
|
2742
|
-
await removePath(
|
|
2743
|
-
await removePath(
|
|
2751
|
+
await removePath(join14(root, "features", "analytics"));
|
|
2752
|
+
await removeEnvKey(join14(root, ".env.example"), "NEXT_PUBLIC_POSTHOG_KEY");
|
|
2753
|
+
await removeEnvKey(join14(root, ".env.example"), "NEXT_PUBLIC_POSTHOG_HOST");
|
|
2754
|
+
await removePath(join14(root, "src", "lib", "posthog.ts"));
|
|
2755
|
+
await removePath(join14(root, "src", "lib", "posthog.js"));
|
|
2744
2756
|
}
|
|
2745
2757
|
if (targets.includes("error-tracking")) {
|
|
2746
|
-
await removePath(
|
|
2747
|
-
await removeEnvKey(
|
|
2748
|
-
await removePath(
|
|
2749
|
-
await removePath(
|
|
2758
|
+
await removePath(join14(root, "features", "error-tracking"));
|
|
2759
|
+
await removeEnvKey(join14(root, ".env.example"), "SENTRY_DSN");
|
|
2760
|
+
await removePath(join14(root, "src", "lib", "sentry.ts"));
|
|
2761
|
+
await removePath(join14(root, "src", "lib", "sentry.js"));
|
|
2750
2762
|
}
|
|
2751
2763
|
}
|
|
2752
2764
|
}
|
|
2753
2765
|
|
|
2754
2766
|
// src/cli/commands/remove.ts
|
|
2755
|
-
import { join as
|
|
2767
|
+
import { join as join15 } from "path";
|
|
2756
2768
|
function parseFeature2(feature) {
|
|
2757
2769
|
const parts = feature.split(":");
|
|
2758
2770
|
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
@@ -2811,7 +2823,7 @@ var removeCommand = new Command4("remove").argument("<feature>", "feature to rem
|
|
|
2811
2823
|
await writeProjectConfig(cwd, next);
|
|
2812
2824
|
await syncPackageJson(`${cwd}/package.json`, current, next);
|
|
2813
2825
|
const readme = buildProjectReadme(next);
|
|
2814
|
-
await writeTextFile(
|
|
2826
|
+
await writeTextFile(join15(cwd, "README.md"), readme + "\n");
|
|
2815
2827
|
logger.info(`Removed ${feature}`);
|
|
2816
2828
|
} catch (err) {
|
|
2817
2829
|
logger.error(err instanceof Error ? err.message : String(err));
|
|
@@ -2821,13 +2833,13 @@ var removeCommand = new Command4("remove").argument("<feature>", "feature to rem
|
|
|
2821
2833
|
|
|
2822
2834
|
// src/cli/commands/update.ts
|
|
2823
2835
|
import { Command as Command5 } from "commander";
|
|
2824
|
-
import { join as
|
|
2836
|
+
import { join as join16 } from "path";
|
|
2825
2837
|
import { readFile as readFile5 } from "fs/promises";
|
|
2826
2838
|
var updateCommand = new Command5("update").option("--check", "check for updates").option("--major", "allow major updates").option("--live", "compare against latest registry versions").action(async (options) => {
|
|
2827
2839
|
try {
|
|
2828
2840
|
const cwd = process.cwd();
|
|
2829
2841
|
const config = await readProjectConfig(cwd);
|
|
2830
|
-
const pkgPath =
|
|
2842
|
+
const pkgPath = join16(cwd, "package.json");
|
|
2831
2843
|
if (options.check) {
|
|
2832
2844
|
const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
|
|
2833
2845
|
const expectedScripts = resolveScripts(config);
|
|
@@ -2897,7 +2909,7 @@ var updateCommand = new Command5("update").option("--check", "check for updates"
|
|
|
2897
2909
|
await writePackageJson(pkgPath, pkg);
|
|
2898
2910
|
}
|
|
2899
2911
|
const readme = buildProjectReadme(config);
|
|
2900
|
-
await writeTextFile(
|
|
2912
|
+
await writeTextFile(join16(cwd, "README.md"), readme + "\n");
|
|
2901
2913
|
logger.info("Project updated.");
|
|
2902
2914
|
} catch (err) {
|
|
2903
2915
|
logger.error(err instanceof Error ? err.message : String(err));
|
|
@@ -2912,13 +2924,13 @@ function parseMajor(version2) {
|
|
|
2912
2924
|
|
|
2913
2925
|
// src/cli/commands/doctor.ts
|
|
2914
2926
|
import { Command as Command6 } from "commander";
|
|
2915
|
-
import { existsSync as
|
|
2916
|
-
import { join as
|
|
2927
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2928
|
+
import { join as join18 } from "path";
|
|
2917
2929
|
|
|
2918
2930
|
// src/utils/doctor.ts
|
|
2919
|
-
import { existsSync as
|
|
2931
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2920
2932
|
import { readFile as readFile6 } from "fs/promises";
|
|
2921
|
-
import { join as
|
|
2933
|
+
import { join as join17, dirname as dirname5, basename } from "path";
|
|
2922
2934
|
function requiredEnvKeys(config) {
|
|
2923
2935
|
const keys = [];
|
|
2924
2936
|
if (config.database.provider !== "none") keys.push("DATABASE_URL");
|
|
@@ -2947,21 +2959,21 @@ function agentFiles(agent) {
|
|
|
2947
2959
|
return files;
|
|
2948
2960
|
}
|
|
2949
2961
|
async function checkProject(cwd) {
|
|
2950
|
-
const configPath =
|
|
2951
|
-
const pkgPath =
|
|
2952
|
-
const envPath =
|
|
2962
|
+
const configPath = join17(cwd, "stackforge.json");
|
|
2963
|
+
const pkgPath = join17(cwd, "package.json");
|
|
2964
|
+
const envPath = join17(cwd, ".env.example");
|
|
2953
2965
|
const issues = [];
|
|
2954
|
-
if (!
|
|
2966
|
+
if (!existsSync4(configPath)) {
|
|
2955
2967
|
return {
|
|
2956
2968
|
issues: ["Missing stackforge.json. Run from a StackForge project root."],
|
|
2957
2969
|
config: void 0,
|
|
2958
2970
|
pkgPath,
|
|
2959
2971
|
envPath,
|
|
2960
2972
|
hasConfig: false,
|
|
2961
|
-
hasPackageJson:
|
|
2973
|
+
hasPackageJson: existsSync4(pkgPath)
|
|
2962
2974
|
};
|
|
2963
2975
|
}
|
|
2964
|
-
if (!
|
|
2976
|
+
if (!existsSync4(pkgPath)) {
|
|
2965
2977
|
return {
|
|
2966
2978
|
issues: ["Missing package.json"],
|
|
2967
2979
|
config: await readProjectConfig(cwd),
|
|
@@ -2991,7 +3003,7 @@ async function checkProject(cwd) {
|
|
|
2991
3003
|
}
|
|
2992
3004
|
}
|
|
2993
3005
|
let envContent = "";
|
|
2994
|
-
if (
|
|
3006
|
+
if (existsSync4(envPath)) {
|
|
2995
3007
|
envContent = await readFile6(envPath, "utf8");
|
|
2996
3008
|
}
|
|
2997
3009
|
const envKeys = new Set(
|
|
@@ -3004,25 +3016,25 @@ async function checkProject(cwd) {
|
|
|
3004
3016
|
}
|
|
3005
3017
|
}
|
|
3006
3018
|
for (const agent of config.aiAgents) {
|
|
3007
|
-
const agentRoot =
|
|
3019
|
+
const agentRoot = join17(cwd, ".ai-agents", agent);
|
|
3008
3020
|
for (const file of agentFiles(agent)) {
|
|
3009
|
-
if (!
|
|
3021
|
+
if (!existsSync4(join17(agentRoot, file))) {
|
|
3010
3022
|
issues.push(`Missing AI agent file: .ai-agents/${agent}/${file}`);
|
|
3011
3023
|
}
|
|
3012
3024
|
}
|
|
3013
|
-
if (agent === "claude" && !
|
|
3025
|
+
if (agent === "claude" && !existsSync4(join17(cwd, ".claude", "claude_desktop_config.json"))) {
|
|
3014
3026
|
issues.push("Missing Claude config: .claude/claude_desktop_config.json");
|
|
3015
3027
|
}
|
|
3016
|
-
if (agent === "codex" && !
|
|
3028
|
+
if (agent === "codex" && !existsSync4(join17(cwd, ".codex", "functions.json"))) {
|
|
3017
3029
|
issues.push("Missing Codex config: .codex/functions.json");
|
|
3018
3030
|
}
|
|
3019
|
-
if (agent === "cursor" && !
|
|
3031
|
+
if (agent === "cursor" && !existsSync4(join17(cwd, ".cursor", "extensions.json"))) {
|
|
3020
3032
|
issues.push("Missing Cursor config: .cursor/extensions.json");
|
|
3021
3033
|
}
|
|
3022
|
-
if (agent === "windsurf" && !
|
|
3034
|
+
if (agent === "windsurf" && !existsSync4(join17(cwd, ".windsurf", "cascade.json"))) {
|
|
3023
3035
|
issues.push("Missing Windsurf config: .windsurf/cascade.json");
|
|
3024
3036
|
}
|
|
3025
|
-
if (agent === "tabnine" && !
|
|
3037
|
+
if (agent === "tabnine" && !existsSync4(join17(cwd, ".tabnine", "config.json"))) {
|
|
3026
3038
|
issues.push("Missing Tabnine config: .tabnine/config.json");
|
|
3027
3039
|
}
|
|
3028
3040
|
}
|
|
@@ -3039,7 +3051,7 @@ async function fixProject(result, cwd) {
|
|
|
3039
3051
|
const { config, pkgPath, envPath } = result;
|
|
3040
3052
|
if (!config) return;
|
|
3041
3053
|
await syncPackageJson(pkgPath, config, config);
|
|
3042
|
-
const envContent =
|
|
3054
|
+
const envContent = existsSync4(envPath) ? await readFile6(envPath, "utf8") : "";
|
|
3043
3055
|
const envKeys = new Set(
|
|
3044
3056
|
envContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0])
|
|
3045
3057
|
);
|
|
@@ -3048,19 +3060,19 @@ async function fixProject(result, cwd) {
|
|
|
3048
3060
|
await appendEnvLine(envPath, `${key}=""`);
|
|
3049
3061
|
}
|
|
3050
3062
|
if (config.aiAgents.length > 0) {
|
|
3051
|
-
const base = basename(cwd) === config.projectName ?
|
|
3063
|
+
const base = basename(cwd) === config.projectName ? dirname5(cwd) : cwd;
|
|
3052
3064
|
await generateAiAgentConfigs(base, config);
|
|
3053
3065
|
}
|
|
3054
3066
|
const readme = buildProjectReadme(config);
|
|
3055
|
-
await writeTextFile(
|
|
3067
|
+
await writeTextFile(join17(cwd, "README.md"), readme + "\n");
|
|
3056
3068
|
}
|
|
3057
3069
|
|
|
3058
3070
|
// src/cli/commands/doctor.ts
|
|
3059
3071
|
import { readFile as readFile7 } from "fs/promises";
|
|
3060
3072
|
var doctorCommand = new Command6("doctor").option("--fix", "apply non-destructive fixes").action(async (options) => {
|
|
3061
3073
|
const cwd = process.cwd();
|
|
3062
|
-
const configPath =
|
|
3063
|
-
if (!
|
|
3074
|
+
const configPath = join18(cwd, "stackforge.json");
|
|
3075
|
+
if (!existsSync5(configPath)) {
|
|
3064
3076
|
logger.error("Missing stackforge.json. Run from a StackForge project root.");
|
|
3065
3077
|
return;
|
|
3066
3078
|
}
|
|
@@ -3078,8 +3090,8 @@ var doctorCommand = new Command6("doctor").option("--fix", "apply non-destructiv
|
|
|
3078
3090
|
logger.error("Failed to read stackforge.json");
|
|
3079
3091
|
return;
|
|
3080
3092
|
}
|
|
3081
|
-
const pkgPath =
|
|
3082
|
-
if (!
|
|
3093
|
+
const pkgPath = join18(cwd, "package.json");
|
|
3094
|
+
if (!existsSync5(pkgPath)) {
|
|
3083
3095
|
logger.error("Missing package.json");
|
|
3084
3096
|
return;
|
|
3085
3097
|
}
|
|
@@ -3158,11 +3170,11 @@ var listAgentsCommand = new Command10("list-agents").action(async () => {
|
|
|
3158
3170
|
// src/cli/commands/migrate.ts
|
|
3159
3171
|
import { Command as Command11 } from "commander";
|
|
3160
3172
|
import { readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
3161
|
-
import { join as
|
|
3173
|
+
import { join as join19 } from "path";
|
|
3162
3174
|
var migrateCommand = new Command11("migrate").option("--dry-run", "show planned migration without writing").action(async (options) => {
|
|
3163
3175
|
try {
|
|
3164
3176
|
const cwd = process.cwd();
|
|
3165
|
-
const path =
|
|
3177
|
+
const path = join19(cwd, "stackforge.json");
|
|
3166
3178
|
const raw = await readFile8(path, "utf8");
|
|
3167
3179
|
const parsed = JSON.parse(raw);
|
|
3168
3180
|
const current = parsed._schemaVersion ?? 0;
|
|
@@ -3252,7 +3264,7 @@ var fixCommand = new Command15("fix").description("apply safe fixes to project c
|
|
|
3252
3264
|
|
|
3253
3265
|
// src/cli/commands/upgrade.ts
|
|
3254
3266
|
import { Command as Command16 } from "commander";
|
|
3255
|
-
import { join as
|
|
3267
|
+
import { join as join20, dirname as dirname6 } from "path";
|
|
3256
3268
|
var upgradeCommand = new Command16("upgrade").option("--preset <name>", "preset to upgrade to").action(async (options) => {
|
|
3257
3269
|
const cwd = process.cwd();
|
|
3258
3270
|
const current = await readProjectConfig(cwd);
|
|
@@ -3294,8 +3306,8 @@ var upgradeCommand = new Command16("upgrade").option("--preset <name>", "preset
|
|
|
3294
3306
|
await cleanupFeature(cwd, current, "feature", feature);
|
|
3295
3307
|
}
|
|
3296
3308
|
await writeProjectConfig(cwd, next);
|
|
3297
|
-
await syncPackageJson(
|
|
3298
|
-
const root =
|
|
3309
|
+
await syncPackageJson(join20(cwd, "package.json"), current, next);
|
|
3310
|
+
const root = dirname6(cwd);
|
|
3299
3311
|
await generateFrontendFiles(root, next);
|
|
3300
3312
|
await generateUiFiles(root, next);
|
|
3301
3313
|
await generateDatabaseFiles(root, next);
|
|
@@ -3303,7 +3315,7 @@ var upgradeCommand = new Command16("upgrade").option("--preset <name>", "preset
|
|
|
3303
3315
|
await generateApiFiles(root, next);
|
|
3304
3316
|
await generateFeatureFiles(root, next);
|
|
3305
3317
|
const readme = buildProjectReadme(next);
|
|
3306
|
-
await writeTextFile(
|
|
3318
|
+
await writeTextFile(join20(cwd, "README.md"), readme + "\n");
|
|
3307
3319
|
logger.info(`Upgraded project to preset: ${options.preset}`);
|
|
3308
3320
|
});
|
|
3309
3321
|
|