stackkit 0.3.2 → 0.3.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/lib/pm/package-manager.js +50 -18
- package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +14 -81
- package/modules/auth/better-auth/files/express/types/express.d.ts +3 -1
- package/modules/auth/better-auth/files/shared/config/env.ts +11 -2
- package/modules/auth/better-auth/files/shared/lib/auth.ts +5 -2
- package/modules/auth/better-auth/files/shared/utils/email.ts +5 -1
- package/modules/database/prisma/files/lib/prisma.ts +1 -1
- package/modules/database/prisma/files/prisma/schema.prisma +0 -1
- package/package.json +1 -1
- package/templates/nextjs/README.md +0 -15
- package/templates/nextjs/lib/api/http.ts +2 -10
- package/templates/react/README.md +0 -13
- package/templates/react/src/shared/api/http.ts +3 -11
|
@@ -66,30 +66,62 @@ async function detectPackageManager(cwd) {
|
|
|
66
66
|
async function installDependencies(cwd, pm, maxRetries = 2) {
|
|
67
67
|
const args = ["install"];
|
|
68
68
|
const stdio = "pipe";
|
|
69
|
+
const packageInstallTimeout = Number(process.env.STACKKIT_INSTALL_TIMEOUT_MS) || constants_1.TIMEOUTS.PACKAGE_INSTALL;
|
|
70
|
+
const envFallback = process.env.STACKKIT_FALLBACK_PMS
|
|
71
|
+
? process.env.STACKKIT_FALLBACK_PMS.split(",")
|
|
72
|
+
.map((s) => s.trim())
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
: [];
|
|
75
|
+
const defaultOrder = ["pnpm", "npm", "yarn", "bun"];
|
|
76
|
+
const preferredOrder = Array.from(new Set([pm, ...envFallback, ...defaultOrder].map((s) => s)));
|
|
69
77
|
let lastError = null;
|
|
70
|
-
|
|
78
|
+
async function isAvailable(pmCheck) {
|
|
71
79
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
await (0, execa_1.default)(pmCheck, ["--version"], { cwd, stdio: "pipe", timeout: 2000 });
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const pmCandidate of preferredOrder) {
|
|
88
|
+
if (!["pnpm", "npm", "yarn", "bun"].includes(pmCandidate))
|
|
89
|
+
continue;
|
|
90
|
+
const available = await isAvailable(pmCandidate);
|
|
91
|
+
if (!available) {
|
|
92
|
+
logger_1.logger.debug(`${pmCandidate} not found on PATH, skipping`);
|
|
93
|
+
continue;
|
|
78
94
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
logger_1.logger.debug(`Attempting install with ${pmCandidate}`);
|
|
96
|
+
let succeeded = false;
|
|
97
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
98
|
+
try {
|
|
99
|
+
if (attempt > 0) {
|
|
100
|
+
logger_1.logger.debug(`Retry attempt ${attempt} for ${pmCandidate}`);
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, constants_1.TIMEOUTS.RETRY_DELAY_BASE * attempt));
|
|
102
|
+
}
|
|
103
|
+
await (0, execa_1.default)(pmCandidate, args, { cwd, stdio, timeout: packageInstallTimeout });
|
|
104
|
+
succeeded = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
lastError = error;
|
|
109
|
+
logger_1.logger.debug(`Installation attempt ${attempt + 1} with ${pmCandidate} failed: ${lastError.message}`);
|
|
110
|
+
const err = error;
|
|
111
|
+
const errorMsg = err.message || "";
|
|
112
|
+
if (errorMsg.includes("ECONNRESET") ||
|
|
113
|
+
errorMsg.includes("ETIMEDOUT") ||
|
|
114
|
+
errorMsg.includes("ENOTFOUND")) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
88
118
|
}
|
|
89
|
-
throw error;
|
|
90
119
|
}
|
|
120
|
+
if (succeeded)
|
|
121
|
+
return;
|
|
122
|
+
logger_1.logger.debug(`${pmCandidate} failed after retries, trying next fallback`);
|
|
91
123
|
}
|
|
92
|
-
throw new Error(`Failed to install dependencies after
|
|
124
|
+
throw new Error(`Failed to install dependencies after trying fallback package managers: ${lastError?.message || "Unknown error"}`);
|
|
93
125
|
}
|
|
94
126
|
async function addDependencies(cwd, pm, packages, dev = false) {
|
|
95
127
|
if (packages.length === 0) {
|
|
@@ -1,91 +1,24 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en"
|
|
2
|
+
<html lang="en">
|
|
3
3
|
|
|
4
4
|
<head>
|
|
5
|
-
<meta
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<
|
|
8
|
-
<title>
|
|
9
|
-
<%= appName || "Your App" %> - Continue with Google
|
|
10
|
-
</title>
|
|
7
|
+
<title>Continue Sign-in</title>
|
|
11
8
|
</head>
|
|
12
9
|
|
|
13
|
-
<body style="
|
|
14
|
-
<
|
|
15
|
-
Continue your Google sign-in securely.
|
|
16
|
-
</div>
|
|
17
|
-
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"
|
|
18
|
-
style="background-color:#f3f4f6; padding:24px 12px;">
|
|
19
|
-
<tr>
|
|
20
|
-
<td align="center">
|
|
21
|
-
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"
|
|
22
|
-
style="max-width:600px; background:#ffffff; border:1px solid #e5e7eb; border-radius:12px; overflow:hidden;">
|
|
23
|
-
<tr>
|
|
24
|
-
<td style="padding:28px 24px 16px 24px; font-family:Arial, Helvetica, sans-serif; color:#111827;">
|
|
25
|
-
<h1 style="margin:0; font-size:22px; line-height:30px; font-weight:700;">Continue with
|
|
26
|
-
Google</h1>
|
|
27
|
-
<p style="margin:14px 0 0; font-size:15px; line-height:24px; color:#374151;">
|
|
28
|
-
Hi <%= userName || "there" %>,
|
|
29
|
-
</p>
|
|
30
|
-
<p style="margin:10px 0 0; font-size:15px; line-height:24px; color:#374151;">
|
|
31
|
-
We received a request to continue authentication with Google for your <strong>
|
|
32
|
-
<%= appName || "account" %>
|
|
33
|
-
</strong>.
|
|
34
|
-
</p>
|
|
35
|
-
</td>
|
|
36
|
-
</tr>
|
|
10
|
+
<body style="font-family: Arial, Helvetica, sans-serif; padding: 24px;">
|
|
11
|
+
<p>Redirecting to <%= provider %> login...</p>
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<a href="<%= redirectUrl %>" target="_blank"
|
|
44
|
-
style="display:inline-block; padding:12px 22px; font-size:15px; line-height:20px; font-weight:600; color:#ffffff; text-decoration:none;">
|
|
45
|
-
Continue Sign-in
|
|
46
|
-
</a>
|
|
47
|
-
</td>
|
|
48
|
-
</tr>
|
|
49
|
-
</table>
|
|
13
|
+
<form id="social-login-form" method="POST" action="<%= signInEndpoint %>">
|
|
14
|
+
<input type="hidden" name="provider" value="<%= provider %>" />
|
|
15
|
+
<input type="hidden" name="callbackURL" value="<%= callbackURL %>" />
|
|
16
|
+
<button type="submit">Continue</button>
|
|
17
|
+
</form>
|
|
50
18
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
style="color:#2563eb; text-decoration:underline;">
|
|
56
|
-
<%= redirectUrl %>
|
|
57
|
-
</a>
|
|
58
|
-
</p>
|
|
59
|
-
</td>
|
|
60
|
-
</tr>
|
|
61
|
-
|
|
62
|
-
<tr>
|
|
63
|
-
<td style="padding:0 24px 24px 24px; font-family:Arial, Helvetica, sans-serif; color:#4b5563;">
|
|
64
|
-
<p style="margin:0; font-size:14px; line-height:22px;">
|
|
65
|
-
If you didn’t request this, you can safely ignore this email. No changes will be made
|
|
66
|
-
unless you continue.
|
|
67
|
-
</p>
|
|
68
|
-
</td>
|
|
69
|
-
</tr>
|
|
70
|
-
|
|
71
|
-
<tr>
|
|
72
|
-
<td
|
|
73
|
-
style="padding:16px 24px 24px 24px; border-top:1px solid #e5e7eb; font-family:Arial, Helvetica, sans-serif; color:#6b7280;">
|
|
74
|
-
<p style="margin:0; font-size:12px; line-height:18px;">
|
|
75
|
-
Need help? Contact us at
|
|
76
|
-
<a href="mailto:<%= supportEmail || " support@example.com" %>" style="color:#2563eb;
|
|
77
|
-
text-decoration:underline;"><%= supportEmail || "support@example.com" %></a>.
|
|
78
|
-
</p>
|
|
79
|
-
<p style="margin:6px 0 0; font-size:12px; line-height:18px;">
|
|
80
|
-
© <%= year || new Date().getFullYear() %>
|
|
81
|
-
<%= appName || "Your App" %>. All rights reserved.
|
|
82
|
-
</p>
|
|
83
|
-
</td>
|
|
84
|
-
</tr>
|
|
85
|
-
</table>
|
|
86
|
-
</td>
|
|
87
|
-
</tr>
|
|
88
|
-
</table>
|
|
89
|
-
</body>
|
|
19
|
+
<script>
|
|
20
|
+
document.getElementById("social-login-form")?.submit();
|
|
21
|
+
</script>
|
|
22
|
+
</body>
|
|
90
23
|
|
|
91
24
|
</html>
|
|
@@ -8,9 +8,12 @@ dotenv.config({ path: path.join(process.cwd(), ".env") });
|
|
|
8
8
|
{{/if}}
|
|
9
9
|
|
|
10
10
|
interface EnvConfig {
|
|
11
|
-
APP_URL
|
|
11
|
+
APP_URL: string;
|
|
12
|
+
{{#if framework == "nextjs"}}
|
|
13
|
+
API_URL: string;
|
|
14
|
+
{{/if}}
|
|
12
15
|
DATABASE_URL: string;
|
|
13
|
-
FRONTEND_URL
|
|
16
|
+
FRONTEND_URL: string;
|
|
14
17
|
BETTER_AUTH_URL: string;
|
|
15
18
|
BETTER_AUTH_SECRET: string;
|
|
16
19
|
GOOGLE_CLIENT_ID: string;
|
|
@@ -40,6 +43,9 @@ interface EnvConfig {
|
|
|
40
43
|
const loadEnvVars = (): EnvConfig => {
|
|
41
44
|
const requiredEnvVars = [
|
|
42
45
|
"APP_URL",
|
|
46
|
+
{{#if framework == "nextjs"}}
|
|
47
|
+
"API_URL",
|
|
48
|
+
{{/if}}
|
|
43
49
|
"DATABASE_URL",
|
|
44
50
|
"FRONTEND_URL",
|
|
45
51
|
"BETTER_AUTH_URL",
|
|
@@ -83,6 +89,9 @@ const loadEnvVars = (): EnvConfig => {
|
|
|
83
89
|
|
|
84
90
|
return {
|
|
85
91
|
APP_URL: process.env.APP_URL as string,
|
|
92
|
+
{{#if framework == "nextjs"}}
|
|
93
|
+
API_URL: process.env.API_URL as string,
|
|
94
|
+
{{/if}}
|
|
86
95
|
DATABASE_URL: process.env.DATABASE_URL as string,
|
|
87
96
|
FRONTEND_URL: process.env.FRONTEND_URL as string,
|
|
88
97
|
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL as string,
|
|
@@ -9,7 +9,7 @@ import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
|
9
9
|
{{/if}}
|
|
10
10
|
{{#if combo == "prisma:nextjs"}}
|
|
11
11
|
import { Role, UserStatus } from "@prisma/client";
|
|
12
|
-
import { sendEmail } from "
|
|
12
|
+
import { sendEmail } from "@/lib/utils/email";
|
|
13
13
|
import { prisma } from "../database/prisma";
|
|
14
14
|
import { envVars } from "@/lib/env";
|
|
15
15
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
@@ -48,7 +48,10 @@ export const auth = betterAuth({
|
|
|
48
48
|
baseURL: envVars.BETTER_AUTH_URL,
|
|
49
49
|
secret: envVars.BETTER_AUTH_SECRET,
|
|
50
50
|
trustedOrigins: [
|
|
51
|
-
envVars.
|
|
51
|
+
envVars.APP_URL!,
|
|
52
|
+
envVars.FRONTEND_URL!,
|
|
53
|
+
envVars.BETTER_AUTH_URL!,
|
|
54
|
+
"http://localhost:3000",
|
|
52
55
|
],
|
|
53
56
|
emailAndPassword: {
|
|
54
57
|
enabled: true,
|
|
@@ -65,6 +65,10 @@ export const sendEmail = async ({
|
|
|
65
65
|
})),
|
|
66
66
|
});
|
|
67
67
|
} catch {
|
|
68
|
-
|
|
68
|
+
{{#if framework == "express"}}
|
|
69
|
+
throw new AppError(status.INTERNAL_SERVER_ERROR, `Failed to send email to ${to}`);
|
|
70
|
+
{{else}}
|
|
71
|
+
throw new Error(`Failed to send email to ${to}`);
|
|
72
|
+
{{/if}}
|
|
69
73
|
}
|
|
70
74
|
};
|
package/package.json
CHANGED
|
@@ -35,21 +35,6 @@ npm run dev
|
|
|
35
35
|
- `npm run start` - Start production server
|
|
36
36
|
- `npm run lint` - Run linter
|
|
37
37
|
|
|
38
|
-
## Project Structure
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
app/
|
|
42
|
-
├── globals.css # Global styles
|
|
43
|
-
├── layout.tsx # Root layout
|
|
44
|
-
├── page.tsx # Home page
|
|
45
|
-
└── api/ # API routes
|
|
46
|
-
|
|
47
|
-
lib/
|
|
48
|
-
└── utils.ts # Utility functions
|
|
49
|
-
|
|
50
|
-
public/ # Static assets
|
|
51
|
-
```
|
|
52
|
-
|
|
53
38
|
## Recommended Folder & File Structure
|
|
54
39
|
|
|
55
40
|
```text
|
|
@@ -12,16 +12,8 @@ const api = axios.create({
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
api.interceptors.request.use(
|
|
15
|
-
(config) =>
|
|
16
|
-
|
|
17
|
-
if (token) {
|
|
18
|
-
(config.headers as Record<string, string>).Authorization = `Bearer ${token}`;
|
|
19
|
-
}
|
|
20
|
-
return config;
|
|
21
|
-
},
|
|
22
|
-
(error: AxiosError) => {
|
|
23
|
-
return Promise.reject(error);
|
|
24
|
-
},
|
|
15
|
+
(config) => config,
|
|
16
|
+
(error: AxiosError) => Promise.reject(error),
|
|
25
17
|
);
|
|
26
18
|
|
|
27
19
|
api.interceptors.response.use(
|
|
@@ -48,19 +48,6 @@ VITE_API_URL=http://localhost:3000/api
|
|
|
48
48
|
VITE_APP_NAME=My App
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
## Project Structure
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
src/
|
|
55
|
-
├── api/ # API client
|
|
56
|
-
├── components/ # UI components
|
|
57
|
-
├── hooks/ # Custom hooks
|
|
58
|
-
├── lib/ # Utilities
|
|
59
|
-
├── pages/ # Route pages
|
|
60
|
-
├── types/ # TypeScript types
|
|
61
|
-
└── utils/ # Helper functions
|
|
62
|
-
```
|
|
63
|
-
|
|
64
51
|
## Recommended Folder & File Structure
|
|
65
52
|
|
|
66
53
|
```text
|
|
@@ -3,7 +3,7 @@ import axios, { AxiosError } from "axios";
|
|
|
3
3
|
import toast from "react-hot-toast";
|
|
4
4
|
|
|
5
5
|
const api = axios.create({
|
|
6
|
-
baseURL: import.meta.env.VITE_API_URL || "http://localhost:
|
|
6
|
+
baseURL: import.meta.env.VITE_API_URL || "http://localhost:5000/api",
|
|
7
7
|
timeout: 10000,
|
|
8
8
|
headers: {
|
|
9
9
|
"Content-Type": "application/json",
|
|
@@ -11,16 +11,8 @@ const api = axios.create({
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
api.interceptors.request.use(
|
|
14
|
-
(config) =>
|
|
15
|
-
|
|
16
|
-
if (token) {
|
|
17
|
-
(config.headers as Record<string, string>).Authorization = `Bearer ${token}`;
|
|
18
|
-
}
|
|
19
|
-
return config;
|
|
20
|
-
},
|
|
21
|
-
(error: AxiosError) => {
|
|
22
|
-
return Promise.reject(error);
|
|
23
|
-
},
|
|
14
|
+
(config) => config,
|
|
15
|
+
(error: AxiosError) => Promise.reject(error),
|
|
24
16
|
);
|
|
25
17
|
|
|
26
18
|
api.interceptors.response.use(
|