create-stackkit-app 0.4.1 → 0.4.3
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/README.md +27 -7
- package/bin/create-stackkit.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/create-project.js +408 -139
- package/dist/lib/template-composer.js +22 -22
- package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +13 -0
- package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +15 -0
- package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +15 -0
- package/modules/auth/better-auth-express/files/lib/auth.ts +1 -1
- package/modules/auth/better-auth-express/files/routes/auth.ts +3 -3
- package/modules/auth/better-auth-express/files/schemas/prisma-mongodb-schema.prisma +72 -0
- package/modules/auth/better-auth-express/files/schemas/prisma-postgresql-schema.prisma +72 -0
- package/modules/auth/better-auth-express/module.json +26 -3
- package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +24 -0
- package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +26 -0
- package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +26 -0
- package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +2 -3
- package/modules/auth/better-auth-nextjs/files/lib/auth.ts +4 -4
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-mongodb-schema.prisma +72 -0
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-postgresql-schema.prisma +72 -0
- package/modules/auth/better-auth-nextjs/module.json +26 -5
- package/modules/auth/better-auth-react/files/lib/auth-client.ts +2 -2
- package/modules/auth/better-auth-react/module.json +7 -5
- package/modules/auth/clerk-express/files/lib/auth.ts +1 -1
- package/modules/auth/clerk-express/module.json +23 -8
- package/modules/auth/clerk-nextjs/files/lib/auth-provider.tsx +1 -1
- package/modules/auth/clerk-nextjs/files/middleware.ts +3 -3
- package/modules/auth/clerk-nextjs/module.json +51 -14
- package/modules/auth/clerk-react/files/lib/auth-provider.tsx +2 -2
- package/modules/auth/clerk-react/module.json +17 -7
- package/modules/database/mongoose-mongodb/files/lib/db.ts +3 -3
- package/modules/database/mongoose-mongodb/module.json +44 -6
- package/modules/database/prisma-mongodb/files/lib/db.ts +2 -2
- package/modules/database/prisma-mongodb/files/prisma/schema.prisma +1 -1
- package/modules/database/prisma-mongodb/module.json +28 -4
- package/modules/database/prisma-postgresql/files/lib/db.ts +2 -2
- package/modules/database/prisma-postgresql/files/prisma/schema.prisma +1 -1
- package/modules/database/prisma-postgresql/module.json +28 -4
- package/package.json +8 -3
- package/templates/express/.env.example +11 -0
- package/templates/express/eslint.config.cjs +42 -0
- package/templates/express/package.json +39 -0
- package/templates/express/src/app.ts +71 -0
- package/templates/express/src/config/env.ts +23 -0
- package/templates/express/src/middlewares/error.middleware.ts +18 -0
- package/templates/{bases/express-base → express}/src/server.ts +2 -2
- package/templates/express/template.json +44 -0
- package/templates/express/tsconfig.json +31 -0
- package/templates/{bases/nextjs-base → nextjs}/app/layout.tsx +1 -5
- package/templates/nextjs/app/page.tsx +57 -0
- package/templates/{bases/nextjs-base → nextjs}/package.json +2 -1
- package/templates/{bases/nextjs-base → nextjs}/template.json +13 -1
- package/templates/react-vite/.env.example +2 -0
- package/templates/react-vite/README.md +85 -0
- package/templates/react-vite/eslint.config.js +23 -0
- package/templates/{bases/react-vite-base → react-vite}/index.html +1 -0
- package/templates/{bases/react-vite-base → react-vite}/package.json +16 -2
- package/templates/react-vite/src/api/client.ts +47 -0
- package/templates/react-vite/src/api/services/user.service.ts +18 -0
- package/templates/react-vite/src/components/ErrorBoundary.tsx +51 -0
- package/templates/react-vite/src/components/Layout.tsx +13 -0
- package/templates/react-vite/src/components/Loading.tsx +8 -0
- package/templates/react-vite/src/components/SEO.tsx +49 -0
- package/templates/react-vite/src/config/constants.ts +5 -0
- package/templates/react-vite/src/hooks/index.ts +64 -0
- package/templates/react-vite/src/index.css +1 -0
- package/templates/react-vite/src/lib/queryClient.ts +12 -0
- package/templates/react-vite/src/main.tsx +22 -0
- package/templates/react-vite/src/pages/About.tsx +78 -0
- package/templates/react-vite/src/pages/Home.tsx +49 -0
- package/templates/react-vite/src/pages/NotFound.tsx +24 -0
- package/templates/react-vite/src/pages/UserProfile.tsx +40 -0
- package/templates/react-vite/src/router.tsx +33 -0
- package/templates/react-vite/src/types/api.d.ts +20 -0
- package/templates/react-vite/src/types/user.d.ts +6 -0
- package/templates/react-vite/src/utils/helpers.ts +51 -0
- package/templates/react-vite/src/utils/storage.ts +35 -0
- package/templates/react-vite/src/vite-env.d.ts +11 -0
- package/templates/react-vite/template.json +46 -0
- package/templates/react-vite/tsconfig.json +4 -0
- package/templates/react-vite/vite.config.ts +13 -0
- package/modules/database/drizzle-postgresql/files/drizzle.config.ts +0 -10
- package/modules/database/drizzle-postgresql/files/lib/db.ts +0 -7
- package/modules/database/drizzle-postgresql/files/lib/schema.ts +0 -8
- package/modules/database/drizzle-postgresql/module.json +0 -34
- package/templates/bases/express-base/.env.example +0 -2
- package/templates/bases/express-base/package.json +0 -23
- package/templates/bases/express-base/src/app.ts +0 -34
- package/templates/bases/express-base/src/config/env.ts +0 -14
- package/templates/bases/express-base/src/middlewares/error.middleware.ts +0 -12
- package/templates/bases/express-base/template.json +0 -7
- package/templates/bases/express-base/tsconfig.json +0 -14
- package/templates/bases/nextjs-base/app/page.tsx +0 -65
- package/templates/bases/react-vite-base/README.md +0 -73
- package/templates/bases/react-vite-base/eslint.config.js +0 -23
- package/templates/bases/react-vite-base/src/App.css +0 -42
- package/templates/bases/react-vite-base/src/App.tsx +0 -35
- package/templates/bases/react-vite-base/src/index.css +0 -68
- package/templates/bases/react-vite-base/src/main.tsx +0 -10
- package/templates/bases/react-vite-base/template.json +0 -19
- package/templates/bases/react-vite-base/tsconfig.json +0 -7
- package/templates/bases/react-vite-base/vite.config.ts +0 -7
- /package/templates/{bases/nextjs-base → nextjs}/README.md +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/app/favicon.ico +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/app/globals.css +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/eslint.config.mjs +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/next.config.ts +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/postcss.config.mjs +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/file.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/globe.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/next.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/vercel.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/window.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/tsconfig.json +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/public/vite.svg +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/src/assets/react.svg +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/tsconfig.app.json +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/tsconfig.node.json +0 -0
|
@@ -12,14 +12,14 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
13
|
const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
|
|
14
14
|
async function createProject(projectName) {
|
|
15
|
-
console.log(chalk_1.default.bold.cyan(
|
|
15
|
+
console.log(chalk_1.default.bold.cyan("\n Create StackKit App\n"));
|
|
16
16
|
// Get project configuration through wizard
|
|
17
17
|
const config = await getProjectConfig(projectName);
|
|
18
18
|
// Validate target directory
|
|
19
19
|
const targetDir = path_1.default.join(process.cwd(), config.projectName);
|
|
20
20
|
if (await fs_extra_1.default.pathExists(targetDir)) {
|
|
21
21
|
console.log(chalk_1.default.red(`\n✖ Directory "${config.projectName}" already exists`));
|
|
22
|
-
console.log(chalk_1.default.gray(
|
|
22
|
+
console.log(chalk_1.default.gray("Please choose a different name or remove the existing directory.\n"));
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
25
|
// Create project
|
|
@@ -30,99 +30,103 @@ async function createProject(projectName) {
|
|
|
30
30
|
async function getProjectConfig(projectName) {
|
|
31
31
|
const answers = await inquirer_1.default.prompt([
|
|
32
32
|
{
|
|
33
|
-
type:
|
|
34
|
-
name:
|
|
35
|
-
message:
|
|
36
|
-
default: projectName ||
|
|
33
|
+
type: "input",
|
|
34
|
+
name: "projectName",
|
|
35
|
+
message: "Project name:",
|
|
36
|
+
default: projectName || "my-app",
|
|
37
37
|
when: !projectName,
|
|
38
38
|
validate: (input) => {
|
|
39
39
|
const validation = (0, validate_npm_package_name_1.default)(input);
|
|
40
40
|
if (!validation.validForNewPackages) {
|
|
41
|
-
return validation.errors?.[0] ||
|
|
41
|
+
return validation.errors?.[0] || "Invalid package name";
|
|
42
42
|
}
|
|
43
43
|
if (fs_extra_1.default.existsSync(path_1.default.join(process.cwd(), input))) {
|
|
44
|
-
return
|
|
44
|
+
return "Directory already exists";
|
|
45
45
|
}
|
|
46
46
|
return true;
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
|
-
type:
|
|
51
|
-
name:
|
|
52
|
-
message:
|
|
50
|
+
type: "list",
|
|
51
|
+
name: "framework",
|
|
52
|
+
message: "Select framework:",
|
|
53
53
|
choices: [
|
|
54
|
-
{ name:
|
|
55
|
-
{ name:
|
|
56
|
-
{ name:
|
|
54
|
+
{ name: "Next.js", value: "nextjs" },
|
|
55
|
+
{ name: "Express.js", value: "express" },
|
|
56
|
+
{ name: "React (Vite)", value: "react-vite" },
|
|
57
57
|
],
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
type:
|
|
61
|
-
name:
|
|
62
|
-
message:
|
|
63
|
-
when: (answers) => answers.framework !==
|
|
60
|
+
type: "list",
|
|
61
|
+
name: "database",
|
|
62
|
+
message: "Select database/ORM:",
|
|
63
|
+
when: (answers) => answers.framework !== "react-vite",
|
|
64
64
|
choices: [
|
|
65
|
-
{ name:
|
|
66
|
-
{ name:
|
|
67
|
-
{ name:
|
|
68
|
-
{ name:
|
|
69
|
-
{ name: 'None', value: 'none' },
|
|
65
|
+
{ name: "Prisma + PostgreSQL", value: "prisma-postgresql" },
|
|
66
|
+
{ name: "Prisma + MongoDB", value: "prisma-mongodb" },
|
|
67
|
+
{ name: "Mongoose + MongoDB", value: "mongoose-mongodb" },
|
|
68
|
+
{ name: "None", value: "none" },
|
|
70
69
|
],
|
|
71
70
|
},
|
|
72
71
|
{
|
|
73
|
-
type:
|
|
74
|
-
name:
|
|
75
|
-
message:
|
|
72
|
+
type: "list",
|
|
73
|
+
name: "auth",
|
|
74
|
+
message: "Select authentication:",
|
|
76
75
|
choices: (answers) => {
|
|
77
|
-
if (answers.framework ===
|
|
76
|
+
if (answers.framework === "react-vite") {
|
|
78
77
|
return [
|
|
79
|
-
{ name:
|
|
80
|
-
{ name:
|
|
81
|
-
{ name:
|
|
78
|
+
{ name: "Better Auth", value: "better-auth-react" },
|
|
79
|
+
{ name: "Clerk", value: "clerk-react" },
|
|
80
|
+
{ name: "None", value: "none" },
|
|
82
81
|
];
|
|
83
82
|
}
|
|
84
83
|
// Next.js apps
|
|
85
|
-
if (answers.framework ===
|
|
84
|
+
if (answers.framework === "nextjs") {
|
|
86
85
|
return [
|
|
87
|
-
{ name:
|
|
88
|
-
{ name:
|
|
89
|
-
{ name:
|
|
86
|
+
{ name: "Better Auth", value: "better-auth-nextjs" },
|
|
87
|
+
{ name: "Clerk", value: "clerk-nextjs" },
|
|
88
|
+
{ name: "None", value: "none" },
|
|
90
89
|
];
|
|
91
90
|
}
|
|
92
91
|
// Express apps
|
|
93
|
-
if (answers.framework ===
|
|
92
|
+
if (answers.framework === "express") {
|
|
94
93
|
return [
|
|
95
|
-
{ name:
|
|
96
|
-
{ name:
|
|
97
|
-
{ name:
|
|
94
|
+
{ name: "Better Auth", value: "better-auth-express" },
|
|
95
|
+
{ name: "Clerk", value: "clerk-express" },
|
|
96
|
+
{ name: "None", value: "none" },
|
|
98
97
|
];
|
|
99
98
|
}
|
|
100
99
|
// Default - no auth
|
|
101
|
-
return [{ name:
|
|
100
|
+
return [{ name: "None", value: "none" }];
|
|
102
101
|
},
|
|
103
102
|
},
|
|
104
103
|
{
|
|
105
|
-
type:
|
|
106
|
-
name:
|
|
107
|
-
message:
|
|
104
|
+
type: "list",
|
|
105
|
+
name: "language",
|
|
106
|
+
message: "Language:",
|
|
108
107
|
choices: [
|
|
109
|
-
{ name:
|
|
110
|
-
{ name:
|
|
108
|
+
{ name: "TypeScript", value: "typescript" },
|
|
109
|
+
{ name: "JavaScript", value: "javascript" },
|
|
111
110
|
],
|
|
112
|
-
default:
|
|
111
|
+
default: "typescript",
|
|
113
112
|
},
|
|
114
113
|
{
|
|
115
|
-
type:
|
|
116
|
-
name:
|
|
117
|
-
message:
|
|
118
|
-
choices: [
|
|
119
|
-
|
|
114
|
+
type: "list",
|
|
115
|
+
name: "packageManager",
|
|
116
|
+
message: "Package manager:",
|
|
117
|
+
choices: [
|
|
118
|
+
{ name: "pnpm (recommended)", value: "pnpm" },
|
|
119
|
+
{ name: "npm", value: "npm" },
|
|
120
|
+
{ name: "yarn", value: "yarn" },
|
|
121
|
+
{ name: "bun", value: "bun" },
|
|
122
|
+
],
|
|
123
|
+
default: "pnpm",
|
|
120
124
|
},
|
|
121
125
|
]);
|
|
122
126
|
return {
|
|
123
127
|
projectName: projectName || answers.projectName,
|
|
124
128
|
framework: answers.framework,
|
|
125
|
-
database: answers.framework ===
|
|
129
|
+
database: answers.framework === "react-vite" ? "none" : answers.database,
|
|
126
130
|
auth: answers.auth,
|
|
127
131
|
language: answers.language,
|
|
128
132
|
packageManager: answers.packageManager,
|
|
@@ -131,96 +135,96 @@ async function getProjectConfig(projectName) {
|
|
|
131
135
|
async function generateProject(config, targetDir) {
|
|
132
136
|
console.log();
|
|
133
137
|
// Copy and compose template
|
|
134
|
-
const copySpinner = (0, ora_1.default)(
|
|
138
|
+
const copySpinner = (0, ora_1.default)("Creating project files...").start();
|
|
135
139
|
try {
|
|
136
140
|
await composeTemplate(config, targetDir);
|
|
137
|
-
copySpinner.succeed(
|
|
141
|
+
copySpinner.succeed("Project files created");
|
|
138
142
|
}
|
|
139
143
|
catch (error) {
|
|
140
|
-
copySpinner.fail(
|
|
144
|
+
copySpinner.fail("Failed to create project files");
|
|
141
145
|
throw error;
|
|
142
146
|
}
|
|
143
147
|
// Install dependencies
|
|
144
|
-
const installSpinner = (0, ora_1.default)(
|
|
148
|
+
const installSpinner = (0, ora_1.default)("Installing dependencies...").start();
|
|
145
149
|
try {
|
|
146
150
|
await installDependencies(targetDir, config.packageManager);
|
|
147
|
-
installSpinner.succeed(
|
|
151
|
+
installSpinner.succeed("Dependencies installed");
|
|
148
152
|
}
|
|
149
153
|
catch (error) {
|
|
150
|
-
installSpinner.fail(
|
|
154
|
+
installSpinner.fail("Failed to install dependencies");
|
|
151
155
|
throw error;
|
|
152
156
|
}
|
|
153
157
|
// Initialize git
|
|
154
|
-
const gitSpinner = (0, ora_1.default)(
|
|
158
|
+
const gitSpinner = (0, ora_1.default)("Initializing git repository...").start();
|
|
155
159
|
try {
|
|
156
160
|
await initGit(targetDir);
|
|
157
|
-
gitSpinner.succeed(
|
|
161
|
+
gitSpinner.succeed("Git repository initialized");
|
|
158
162
|
}
|
|
159
163
|
catch (error) {
|
|
160
|
-
gitSpinner.warn(
|
|
164
|
+
gitSpinner.warn("Failed to initialize git repository");
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
async function composeTemplate(config, targetDir) {
|
|
164
|
-
const templatesDir = path_1.default.join(__dirname,
|
|
168
|
+
const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
|
|
165
169
|
await fs_extra_1.default.ensureDir(targetDir);
|
|
166
170
|
// 1. Copy base framework template
|
|
167
171
|
await copyBaseFramework(templatesDir, targetDir, config.framework);
|
|
168
172
|
// 2. Merge database configuration
|
|
169
|
-
if (config.database !==
|
|
170
|
-
await mergeDatabaseConfig(templatesDir, targetDir, config.database);
|
|
173
|
+
if (config.database !== "none") {
|
|
174
|
+
await mergeDatabaseConfig(templatesDir, targetDir, config.database, config.framework);
|
|
171
175
|
}
|
|
172
176
|
// 3. Merge auth configuration
|
|
173
|
-
if (config.auth !==
|
|
174
|
-
await mergeAuthConfig(templatesDir, targetDir, config.framework, config.auth);
|
|
177
|
+
if (config.auth !== "none") {
|
|
178
|
+
await mergeAuthConfig(templatesDir, targetDir, config.framework, config.auth, config.database);
|
|
175
179
|
}
|
|
176
180
|
// 4. Update package.json with project name
|
|
177
|
-
const packageJsonPath = path_1.default.join(targetDir,
|
|
181
|
+
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
178
182
|
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
179
183
|
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
180
184
|
packageJson.name = config.projectName;
|
|
181
185
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
182
186
|
}
|
|
183
187
|
// 5. Convert to JavaScript if selected
|
|
184
|
-
if (config.language ===
|
|
185
|
-
await convertToJavaScript(targetDir);
|
|
188
|
+
if (config.language === "javascript") {
|
|
189
|
+
await convertToJavaScript(targetDir, config.framework);
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
192
|
async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
189
|
-
const baseDir = path_1.default.join(templatesDir,
|
|
193
|
+
const baseDir = path_1.default.join(templatesDir, framework);
|
|
190
194
|
if (!(await fs_extra_1.default.pathExists(baseDir))) {
|
|
191
195
|
throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
|
|
192
196
|
}
|
|
193
197
|
await fs_extra_1.default.copy(baseDir, targetDir, {
|
|
194
198
|
filter: (src) => {
|
|
195
199
|
const basename = path_1.default.basename(src);
|
|
196
|
-
return ![
|
|
200
|
+
return !["template.json", "config.json", "node_modules", ".git"].includes(basename);
|
|
197
201
|
},
|
|
198
202
|
});
|
|
199
203
|
}
|
|
200
|
-
async function mergeDatabaseConfig(templatesDir, targetDir, database) {
|
|
204
|
+
async function mergeDatabaseConfig(templatesDir, targetDir, database, framework) {
|
|
201
205
|
// Use modules directory (sibling to templates)
|
|
202
|
-
const modulesDir = path_1.default.join(templatesDir,
|
|
203
|
-
const dbModulePath = path_1.default.join(modulesDir,
|
|
206
|
+
const modulesDir = path_1.default.join(templatesDir, "..", "modules");
|
|
207
|
+
const dbModulePath = path_1.default.join(modulesDir, "database", database);
|
|
204
208
|
if (!(await fs_extra_1.default.pathExists(dbModulePath))) {
|
|
205
209
|
console.warn(`Database module not found: ${database}`);
|
|
206
210
|
return;
|
|
207
211
|
}
|
|
208
212
|
// Read module.json
|
|
209
|
-
const moduleJsonPath = path_1.default.join(dbModulePath,
|
|
213
|
+
const moduleJsonPath = path_1.default.join(dbModulePath, "module.json");
|
|
210
214
|
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
211
215
|
return;
|
|
212
216
|
}
|
|
213
217
|
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
214
218
|
// Copy files from module
|
|
215
|
-
const filesDir = path_1.default.join(dbModulePath,
|
|
219
|
+
const filesDir = path_1.default.join(dbModulePath, "files");
|
|
216
220
|
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
217
221
|
// Copy files based on patches in module.json
|
|
218
222
|
for (const patch of moduleData.patches || []) {
|
|
219
|
-
if (patch.type ===
|
|
223
|
+
if (patch.type === "create-file") {
|
|
220
224
|
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
221
225
|
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
222
226
|
// Simple placeholder replacement for lib
|
|
223
|
-
destFile = destFile.replace(
|
|
227
|
+
destFile = destFile.replace("{{lib}}", "lib").replace("{{src}}", "src");
|
|
224
228
|
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
225
229
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
226
230
|
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
@@ -239,60 +243,68 @@ async function mergeDatabaseConfig(templatesDir, targetDir, database) {
|
|
|
239
243
|
envVars[envVar.key] = envVar.value;
|
|
240
244
|
}
|
|
241
245
|
await mergeEnvFile(targetDir, envVars);
|
|
246
|
+
// Apply framework-specific patches from database module
|
|
247
|
+
if (moduleData.frameworkPatches) {
|
|
248
|
+
const frameworkKey = framework === "react-vite" ? "react" : framework;
|
|
249
|
+
const patches = moduleData.frameworkPatches[frameworkKey];
|
|
250
|
+
if (patches) {
|
|
251
|
+
await applyFrameworkPatches(targetDir, patches);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
242
254
|
}
|
|
243
|
-
async function mergeAuthConfig(templatesDir, targetDir, framework, auth) {
|
|
255
|
+
async function mergeAuthConfig(templatesDir, targetDir, framework, auth, database = "none") {
|
|
244
256
|
// Use modules directory (sibling to templates)
|
|
245
|
-
const modulesDir = path_1.default.join(templatesDir,
|
|
257
|
+
const modulesDir = path_1.default.join(templatesDir, "..", "modules");
|
|
246
258
|
// Auth modules are now named with framework suffix
|
|
247
259
|
// e.g., better-auth-nextjs, authjs-express, better-auth-react
|
|
248
260
|
// If auth already has framework suffix, use it directly
|
|
249
261
|
// Otherwise, map old names to new ones
|
|
250
262
|
const authMap = {
|
|
251
|
-
nextauth:
|
|
252
|
-
|
|
253
|
-
clerk: framework ===
|
|
254
|
-
?
|
|
255
|
-
: framework ===
|
|
256
|
-
?
|
|
257
|
-
:
|
|
263
|
+
nextauth: "nextauth",
|
|
264
|
+
"better-auth": framework === "nextjs" ? "better-auth-nextjs" : "better-auth-express",
|
|
265
|
+
clerk: framework === "nextjs"
|
|
266
|
+
? "clerk-nextjs"
|
|
267
|
+
: framework === "react-vite"
|
|
268
|
+
? "clerk-react"
|
|
269
|
+
: "clerk-express",
|
|
258
270
|
};
|
|
259
|
-
const authKey = auth.includes(
|
|
260
|
-
const authModulePath = path_1.default.join(modulesDir,
|
|
271
|
+
const authKey = auth.includes("-") ? auth : authMap[auth] || auth;
|
|
272
|
+
const authModulePath = path_1.default.join(modulesDir, "auth", authKey);
|
|
261
273
|
if (!(await fs_extra_1.default.pathExists(authModulePath))) {
|
|
262
274
|
console.warn(`Auth module not found: ${authKey}`);
|
|
263
275
|
return;
|
|
264
276
|
}
|
|
265
277
|
// Read module.json
|
|
266
|
-
const moduleJsonPath = path_1.default.join(authModulePath,
|
|
278
|
+
const moduleJsonPath = path_1.default.join(authModulePath, "module.json");
|
|
267
279
|
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
268
280
|
return;
|
|
269
281
|
}
|
|
270
282
|
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
271
283
|
// Copy files from module
|
|
272
|
-
const filesDir = path_1.default.join(authModulePath,
|
|
284
|
+
const filesDir = path_1.default.join(authModulePath, "files");
|
|
273
285
|
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
274
286
|
// Determine path replacements based on framework
|
|
275
287
|
const getReplacements = () => {
|
|
276
|
-
if (framework ===
|
|
277
|
-
return { lib:
|
|
288
|
+
if (framework === "nextjs") {
|
|
289
|
+
return { lib: "lib", router: "app" };
|
|
278
290
|
}
|
|
279
|
-
else if (framework ===
|
|
280
|
-
return { lib:
|
|
291
|
+
else if (framework === "express") {
|
|
292
|
+
return { lib: "src", router: "src" };
|
|
281
293
|
}
|
|
282
294
|
else {
|
|
283
|
-
return { lib:
|
|
295
|
+
return { lib: "src", router: "src" };
|
|
284
296
|
}
|
|
285
297
|
};
|
|
286
298
|
const replacements = getReplacements();
|
|
287
299
|
// Copy files based on patches in module.json
|
|
288
300
|
for (const patch of moduleData.patches || []) {
|
|
289
|
-
if (patch.type ===
|
|
301
|
+
if (patch.type === "create-file") {
|
|
290
302
|
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
291
303
|
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
292
304
|
// Replace placeholders
|
|
293
305
|
destFile = destFile
|
|
294
|
-
.replace(
|
|
295
|
-
.replace(
|
|
306
|
+
.replace("{{lib}}", replacements.lib)
|
|
307
|
+
.replace("{{router}}", replacements.router);
|
|
296
308
|
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
297
309
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
298
310
|
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
@@ -300,6 +312,47 @@ async function mergeAuthConfig(templatesDir, targetDir, framework, auth) {
|
|
|
300
312
|
}
|
|
301
313
|
}
|
|
302
314
|
}
|
|
315
|
+
// Handle database-specific adapters and schemas
|
|
316
|
+
if (database !== "none" && moduleData.databaseAdapters) {
|
|
317
|
+
const adapterConfig = moduleData.databaseAdapters[database];
|
|
318
|
+
if (adapterConfig) {
|
|
319
|
+
// Copy adapter file
|
|
320
|
+
if (adapterConfig.adapter) {
|
|
321
|
+
const adapterSource = path_1.default.join(authModulePath, adapterConfig.adapter);
|
|
322
|
+
const adapterFileName = path_1.default.basename(adapterConfig.adapter);
|
|
323
|
+
// Determine destination based on framework
|
|
324
|
+
let adapterDest;
|
|
325
|
+
if (framework === "nextjs") {
|
|
326
|
+
adapterDest = path_1.default.join(targetDir, "lib", "auth.ts");
|
|
327
|
+
}
|
|
328
|
+
else if (framework === "express") {
|
|
329
|
+
adapterDest = path_1.default.join(targetDir, "src", "auth.ts");
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
adapterDest = path_1.default.join(targetDir, "src", "lib", "auth.ts");
|
|
333
|
+
}
|
|
334
|
+
if (await fs_extra_1.default.pathExists(adapterSource)) {
|
|
335
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(adapterDest));
|
|
336
|
+
await fs_extra_1.default.copy(adapterSource, adapterDest, { overwrite: true });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Copy schema file if it exists
|
|
340
|
+
if (adapterConfig.schema && adapterConfig.schemaDestination) {
|
|
341
|
+
const schemaSource = path_1.default.join(authModulePath, adapterConfig.schema);
|
|
342
|
+
const schemaDest = path_1.default.join(targetDir, adapterConfig.schemaDestination);
|
|
343
|
+
if (await fs_extra_1.default.pathExists(schemaSource)) {
|
|
344
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(schemaDest));
|
|
345
|
+
await fs_extra_1.default.copy(schemaSource, schemaDest, { overwrite: true });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Merge adapter-specific dependencies
|
|
349
|
+
if (adapterConfig.dependencies) {
|
|
350
|
+
await mergePackageJson(targetDir, {
|
|
351
|
+
dependencies: adapterConfig.dependencies,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
303
356
|
// Merge package.json with module dependencies
|
|
304
357
|
await mergePackageJson(targetDir, {
|
|
305
358
|
dependencies: moduleData.dependencies,
|
|
@@ -313,7 +366,7 @@ async function mergeAuthConfig(templatesDir, targetDir, framework, auth) {
|
|
|
313
366
|
await mergeEnvFile(targetDir, envVars);
|
|
314
367
|
}
|
|
315
368
|
async function mergePackageJson(targetDir, config) {
|
|
316
|
-
const pkgPath = path_1.default.join(targetDir,
|
|
369
|
+
const pkgPath = path_1.default.join(targetDir, "package.json");
|
|
317
370
|
if (!(await fs_extra_1.default.pathExists(pkgPath))) {
|
|
318
371
|
return;
|
|
319
372
|
}
|
|
@@ -333,19 +386,19 @@ async function mergeEnvFile(targetDir, envVars) {
|
|
|
333
386
|
if (Object.keys(envVars).length === 0) {
|
|
334
387
|
return;
|
|
335
388
|
}
|
|
336
|
-
const envExamplePath = path_1.default.join(targetDir,
|
|
337
|
-
const envPath = path_1.default.join(targetDir,
|
|
389
|
+
const envExamplePath = path_1.default.join(targetDir, ".env.example");
|
|
390
|
+
const envPath = path_1.default.join(targetDir, ".env");
|
|
338
391
|
const envContent = Object.entries(envVars)
|
|
339
392
|
.map(([key, value]) => `${key}="${value}"`)
|
|
340
|
-
.join(
|
|
393
|
+
.join("\n") + "\n";
|
|
341
394
|
// Update .env.example
|
|
342
395
|
if (await fs_extra_1.default.pathExists(envExamplePath)) {
|
|
343
|
-
const existing = await fs_extra_1.default.readFile(envExamplePath,
|
|
344
|
-
const existingKeys = existing.split(
|
|
396
|
+
const existing = await fs_extra_1.default.readFile(envExamplePath, "utf-8");
|
|
397
|
+
const existingKeys = existing.split("\n").map((line) => line.split("=")[0]);
|
|
345
398
|
const newVars = Object.keys(envVars).filter((key) => !existingKeys.includes(key));
|
|
346
399
|
if (newVars.length > 0) {
|
|
347
|
-
const newContent = newVars.map((key) => `${key}="${envVars[key]}"`).join(
|
|
348
|
-
await fs_extra_1.default.appendFile(envExamplePath,
|
|
400
|
+
const newContent = newVars.map((key) => `${key}="${envVars[key]}"`).join("\n");
|
|
401
|
+
await fs_extra_1.default.appendFile(envExamplePath, "\n" + newContent + "\n");
|
|
349
402
|
}
|
|
350
403
|
}
|
|
351
404
|
else {
|
|
@@ -356,79 +409,295 @@ async function mergeEnvFile(targetDir, envVars) {
|
|
|
356
409
|
await fs_extra_1.default.writeFile(envPath, envContent);
|
|
357
410
|
}
|
|
358
411
|
}
|
|
359
|
-
async function convertToJavaScript(targetDir) {
|
|
360
|
-
// Remove
|
|
361
|
-
const tsFiles = [
|
|
412
|
+
async function convertToJavaScript(targetDir, framework) {
|
|
413
|
+
// Remove TS config and declaration files
|
|
414
|
+
const tsFiles = [
|
|
415
|
+
"tsconfig.json",
|
|
416
|
+
"tsconfig.app.json",
|
|
417
|
+
"tsconfig.node.json",
|
|
418
|
+
"next-env.d.ts",
|
|
419
|
+
"vite-env.d.ts",
|
|
420
|
+
];
|
|
362
421
|
for (const file of tsFiles) {
|
|
363
422
|
const filePath = path_1.default.join(targetDir, file);
|
|
364
423
|
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
365
424
|
await fs_extra_1.default.remove(filePath);
|
|
366
425
|
}
|
|
367
426
|
}
|
|
368
|
-
|
|
369
|
-
|
|
427
|
+
const removeDtsFiles = async (dir) => {
|
|
428
|
+
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
429
|
+
for (const entry of entries) {
|
|
430
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
431
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
432
|
+
await removeDtsFiles(fullPath);
|
|
433
|
+
}
|
|
434
|
+
else if (entry.isFile() && entry.name.endsWith(".d.ts")) {
|
|
435
|
+
await fs_extra_1.default.remove(fullPath);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
await removeDtsFiles(targetDir);
|
|
440
|
+
// Use Babel to strip types only, preserving exact formatting/comments/blank lines, producing clean production-ready code
|
|
441
|
+
const babel = require("@babel/core");
|
|
442
|
+
const transpileAllTsFiles = async (dir) => {
|
|
370
443
|
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
371
444
|
for (const entry of entries) {
|
|
372
445
|
const fullPath = path_1.default.join(dir, entry.name);
|
|
373
|
-
if (entry.isDirectory() && entry.name !==
|
|
374
|
-
await
|
|
446
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
447
|
+
await transpileAllTsFiles(fullPath);
|
|
375
448
|
}
|
|
376
449
|
else if (entry.isFile()) {
|
|
377
|
-
if (entry.name.endsWith(
|
|
378
|
-
await fs_extra_1.default.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
450
|
+
if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
451
|
+
const code = await fs_extra_1.default.readFile(fullPath, "utf8");
|
|
452
|
+
const isTsx = entry.name.endsWith(".tsx");
|
|
453
|
+
const outFile = fullPath.replace(/\.tsx$/, ".jsx").replace(/\.ts$/, ".js");
|
|
454
|
+
const presets = [
|
|
455
|
+
[
|
|
456
|
+
require.resolve("@babel/preset-typescript"),
|
|
457
|
+
{
|
|
458
|
+
onlyRemoveTypeImports: true,
|
|
459
|
+
allowDeclareFields: true,
|
|
460
|
+
allowNamespaces: true,
|
|
461
|
+
optimizeForSpeed: true,
|
|
462
|
+
allExtensions: true,
|
|
463
|
+
isTSX: isTsx,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
[
|
|
467
|
+
require.resolve("@babel/preset-env"),
|
|
468
|
+
{
|
|
469
|
+
targets: { node: "18" },
|
|
470
|
+
modules: false,
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
];
|
|
474
|
+
if (isTsx) {
|
|
475
|
+
presets.push([
|
|
476
|
+
require.resolve("@babel/preset-react"),
|
|
477
|
+
{
|
|
478
|
+
runtime: "automatic",
|
|
479
|
+
},
|
|
480
|
+
]);
|
|
481
|
+
}
|
|
482
|
+
// Use recast + Babel AST transform (same approach as transform.tools)
|
|
483
|
+
try {
|
|
484
|
+
const recast = require("recast");
|
|
485
|
+
const { transformFromAstSync } = require("@babel/core");
|
|
486
|
+
const transformTypescript = require("@babel/plugin-transform-typescript");
|
|
487
|
+
// getBabelOptions may be exported as default or directly
|
|
488
|
+
let getBabelOptions = require("recast/parsers/_babel_options");
|
|
489
|
+
if (getBabelOptions && getBabelOptions.default)
|
|
490
|
+
getBabelOptions = getBabelOptions.default;
|
|
491
|
+
const babelParser = require("recast/parsers/babel").parser;
|
|
492
|
+
const ast = recast.parse(code, {
|
|
493
|
+
parser: {
|
|
494
|
+
parse: (source, options) => {
|
|
495
|
+
const babelOptions = getBabelOptions(options || {});
|
|
496
|
+
// ensure typescript and jsx handling
|
|
497
|
+
if (isTsx) {
|
|
498
|
+
babelOptions.plugins.push("typescript", "jsx");
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
babelOptions.plugins.push("typescript");
|
|
502
|
+
}
|
|
503
|
+
return babelParser.parse(source, babelOptions);
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
const opts = {
|
|
508
|
+
cloneInputAst: false,
|
|
509
|
+
code: false,
|
|
510
|
+
ast: true,
|
|
511
|
+
plugins: [transformTypescript],
|
|
512
|
+
configFile: false,
|
|
513
|
+
};
|
|
514
|
+
const { ast: transformedAST } = transformFromAstSync(ast, code, opts);
|
|
515
|
+
const resultCode = recast.print(transformedAST).code;
|
|
516
|
+
await fs_extra_1.default.writeFile(outFile, resultCode, "utf8");
|
|
517
|
+
await fs_extra_1.default.remove(fullPath);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
// fallback to previous Babel pipeline if anything fails
|
|
522
|
+
}
|
|
523
|
+
const result = await babel.transformAsync(code, {
|
|
524
|
+
filename: entry.name,
|
|
525
|
+
presets,
|
|
526
|
+
comments: true,
|
|
527
|
+
retainLines: true,
|
|
528
|
+
compact: false,
|
|
529
|
+
babelrc: false,
|
|
530
|
+
configFile: false,
|
|
531
|
+
});
|
|
532
|
+
await fs_extra_1.default.writeFile(outFile, result.code, "utf8");
|
|
533
|
+
await fs_extra_1.default.remove(fullPath);
|
|
382
534
|
}
|
|
383
535
|
}
|
|
384
536
|
}
|
|
385
537
|
};
|
|
386
|
-
await
|
|
387
|
-
|
|
388
|
-
const
|
|
538
|
+
await transpileAllTsFiles(targetDir);
|
|
539
|
+
const templatesRoot = path_1.default.join(__dirname, "..", "..", "templates");
|
|
540
|
+
const templateName = framework;
|
|
541
|
+
let fileReplacements = [];
|
|
542
|
+
let jsScripts = null;
|
|
543
|
+
if (templateName) {
|
|
544
|
+
const templateJsonPath = path_1.default.join(templatesRoot, templateName, "template.json");
|
|
545
|
+
if (await fs_extra_1.default.pathExists(templateJsonPath)) {
|
|
546
|
+
try {
|
|
547
|
+
const templateJson = await fs_extra_1.default.readJson(templateJsonPath);
|
|
548
|
+
if (Array.isArray(templateJson.fileReplacements)) {
|
|
549
|
+
fileReplacements = templateJson.fileReplacements;
|
|
550
|
+
}
|
|
551
|
+
if (templateJson.jsScripts) {
|
|
552
|
+
jsScripts = templateJson.jsScripts;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
catch { }
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
for (const rep of fileReplacements) {
|
|
559
|
+
const filePath = path_1.default.join(targetDir, rep.file);
|
|
560
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
561
|
+
let content = await fs_extra_1.default.readFile(filePath, "utf8");
|
|
562
|
+
if (rep.from && rep.to) {
|
|
563
|
+
content = content.replace(rep.from, rep.to);
|
|
564
|
+
await fs_extra_1.default.writeFile(filePath, content, "utf8");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (jsScripts) {
|
|
569
|
+
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
570
|
+
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
571
|
+
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
572
|
+
packageJson.scripts = { ...packageJson.scripts, ...jsScripts };
|
|
573
|
+
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const jsconfig = path_1.default.join(targetDir, "jsconfig.json");
|
|
577
|
+
if (!(await fs_extra_1.default.pathExists(jsconfig))) {
|
|
578
|
+
for (const tmpl of await fs_extra_1.default.readdir(templatesRoot, { withFileTypes: true })) {
|
|
579
|
+
if (tmpl.isDirectory()) {
|
|
580
|
+
const templateJsconfig = path_1.default.join(templatesRoot, tmpl.name, "jsconfig.json");
|
|
581
|
+
if (await fs_extra_1.default.pathExists(templateJsconfig)) {
|
|
582
|
+
await fs_extra_1.default.copy(templateJsconfig, jsconfig);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
const srcDir = path_1.default.join(targetDir, "src");
|
|
589
|
+
if (await fs_extra_1.default.pathExists(srcDir)) {
|
|
590
|
+
const srcFiles = await fs_extra_1.default.readdir(srcDir);
|
|
591
|
+
for (const file of srcFiles) {
|
|
592
|
+
if ((file.endsWith(".js") || file.endsWith(".jsx")) &&
|
|
593
|
+
file.replace(/\.(js|jsx)$/, ".ts") &&
|
|
594
|
+
srcFiles.includes(file.replace(/\.(js|jsx)$/, ".ts"))) {
|
|
595
|
+
await fs_extra_1.default.remove(path_1.default.join(srcDir, file.replace(/\.(js|jsx)$/, ".ts")));
|
|
596
|
+
}
|
|
597
|
+
if (file.endsWith(".jsx") && srcFiles.includes(file.replace(/\.jsx$/, ".tsx"))) {
|
|
598
|
+
await fs_extra_1.default.remove(path_1.default.join(srcDir, file.replace(/\.jsx$/, ".tsx")));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
389
603
|
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
390
604
|
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
391
605
|
if (packageJson.devDependencies) {
|
|
392
|
-
delete packageJson.devDependencies[
|
|
393
|
-
delete packageJson.devDependencies[
|
|
394
|
-
delete packageJson.devDependencies[
|
|
395
|
-
delete packageJson.devDependencies[
|
|
606
|
+
delete packageJson.devDependencies["typescript"];
|
|
607
|
+
delete packageJson.devDependencies["@types/node"];
|
|
608
|
+
delete packageJson.devDependencies["@types/react"];
|
|
609
|
+
delete packageJson.devDependencies["@types/react-dom"];
|
|
396
610
|
}
|
|
397
611
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
398
612
|
}
|
|
399
613
|
}
|
|
400
614
|
async function installDependencies(cwd, packageManager) {
|
|
401
615
|
const commands = {
|
|
402
|
-
npm:
|
|
403
|
-
yarn:
|
|
404
|
-
pnpm:
|
|
616
|
+
npm: "npm install",
|
|
617
|
+
yarn: "yarn install",
|
|
618
|
+
pnpm: "pnpm install",
|
|
619
|
+
bun: "bun install",
|
|
620
|
+
};
|
|
621
|
+
const isAvailable = (cmd) => {
|
|
622
|
+
try {
|
|
623
|
+
(0, child_process_1.execSync)(`command -v ${cmd}`, { stdio: "ignore" });
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
405
629
|
};
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
630
|
+
let chosen = packageManager;
|
|
631
|
+
// If requested package manager is not available, try to fall back to a common one
|
|
632
|
+
if (!isAvailable(chosen)) {
|
|
633
|
+
const fallbacks = ["pnpm", "npm", "yarn", "bun"];
|
|
634
|
+
const found = fallbacks.find((p) => isAvailable(p));
|
|
635
|
+
if (found) {
|
|
636
|
+
console.warn(`Selected package manager '${chosen}' was not found. Falling back to '${found}'.`);
|
|
637
|
+
chosen = found;
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
throw new Error(`Selected package manager '${packageManager}' was not found and no fallback package manager is available. Please install '${packageManager}' or use a different package manager.`);
|
|
641
|
+
}
|
|
409
642
|
}
|
|
643
|
+
const command = commands[chosen];
|
|
410
644
|
(0, child_process_1.execSync)(command, {
|
|
411
645
|
cwd,
|
|
412
|
-
stdio:
|
|
646
|
+
stdio: "pipe",
|
|
413
647
|
});
|
|
414
648
|
}
|
|
415
649
|
async function initGit(cwd) {
|
|
416
650
|
try {
|
|
417
|
-
(0, child_process_1.execSync)(
|
|
418
|
-
(0, child_process_1.execSync)(
|
|
419
|
-
(0, child_process_1.execSync)(
|
|
651
|
+
(0, child_process_1.execSync)("git --version", { stdio: "pipe" });
|
|
652
|
+
(0, child_process_1.execSync)("git init", { cwd, stdio: "pipe" });
|
|
653
|
+
(0, child_process_1.execSync)("git add -A", { cwd, stdio: "pipe" });
|
|
420
654
|
(0, child_process_1.execSync)('git commit -m "Initial commit from create-stackkit-app"', {
|
|
421
655
|
cwd,
|
|
422
|
-
stdio:
|
|
656
|
+
stdio: "pipe",
|
|
423
657
|
});
|
|
424
658
|
}
|
|
425
659
|
catch (error) {
|
|
426
|
-
throw new Error(
|
|
660
|
+
throw new Error("Git initialization failed");
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async function applyFrameworkPatches(targetDir, patches) {
|
|
664
|
+
for (const [filename, patchConfig] of Object.entries(patches)) {
|
|
665
|
+
const filePath = path_1.default.join(targetDir, filename);
|
|
666
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
667
|
+
const fileContent = await fs_extra_1.default.readJson(filePath);
|
|
668
|
+
if (patchConfig.merge) {
|
|
669
|
+
// Deep merge configuration
|
|
670
|
+
const merged = deepMerge(fileContent, patchConfig.merge);
|
|
671
|
+
await fs_extra_1.default.writeJson(filePath, merged, { spaces: 2 });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function deepMerge(target, source) {
|
|
677
|
+
const output = { ...target };
|
|
678
|
+
for (const key in source) {
|
|
679
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
680
|
+
if (target[key]) {
|
|
681
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
output[key] = source[key];
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else if (Array.isArray(source[key])) {
|
|
688
|
+
// For arrays, merge uniquely
|
|
689
|
+
output[key] = Array.from(new Set([...(target[key] || []), ...source[key]]));
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
output[key] = source[key];
|
|
693
|
+
}
|
|
427
694
|
}
|
|
695
|
+
return output;
|
|
428
696
|
}
|
|
429
697
|
function showNextSteps(config) {
|
|
430
698
|
console.log(chalk_1.default.green.bold(`\n✓ Created ${config.projectName}\n`));
|
|
431
|
-
console.log(chalk_1.default.bold(
|
|
699
|
+
console.log(chalk_1.default.bold("Next steps:"));
|
|
432
700
|
console.log(chalk_1.default.cyan(` cd ${config.projectName}`));
|
|
433
|
-
|
|
701
|
+
// Only `bun` is supported as the package manager in production-ready CLI
|
|
702
|
+
console.log(chalk_1.default.cyan(" bun run dev\n"));
|
|
434
703
|
}
|