@techstream/quark-core 1.1.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -398
- package/package.json +33 -28
- package/src/auth/index.js +1 -3
- package/src/auth.test.js +1 -3
- package/src/cache.test.js +1 -1
- package/src/email-templates.js +134 -0
- package/src/email-templates.test.js +124 -0
- package/src/email.js +13 -6
- package/src/email.test.js +138 -126
- package/src/index.js +4 -2
- package/src/mailhog.js +2 -2
- package/src/rate-limiter.js +1 -1
- package/.turbo/turbo-lint.log +0 -7
- package/.turbo/turbo-test.log +0 -1376
- package/test-imports.mjs +0 -21
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { passwordResetEmail, welcomeEmail } from "./email-templates.js";
|
|
4
|
+
|
|
5
|
+
test("welcomeEmail", async (t) => {
|
|
6
|
+
await t.test("returns subject, html, and text", () => {
|
|
7
|
+
const result = welcomeEmail({});
|
|
8
|
+
assert.ok(result.subject);
|
|
9
|
+
assert.ok(result.html);
|
|
10
|
+
assert.ok(result.text);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
await t.test("uses default app name in subject", () => {
|
|
14
|
+
const result = welcomeEmail({});
|
|
15
|
+
assert.strictEqual(result.subject, "Welcome to Quark!");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await t.test("uses custom app name in subject", () => {
|
|
19
|
+
const result = welcomeEmail({ appName: "MyApp" });
|
|
20
|
+
assert.strictEqual(result.subject, "Welcome to MyApp!");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await t.test("includes user name in html when provided", () => {
|
|
24
|
+
const result = welcomeEmail({ name: "Alice" });
|
|
25
|
+
assert.ok(result.html.includes("Alice"));
|
|
26
|
+
assert.ok(result.text.includes("Alice"));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await t.test("uses fallback greeting when no name provided", () => {
|
|
30
|
+
const result = welcomeEmail({});
|
|
31
|
+
assert.ok(result.html.includes("there"));
|
|
32
|
+
assert.ok(result.text.includes("there"));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await t.test("includes login URL when provided", () => {
|
|
36
|
+
const result = welcomeEmail({ loginUrl: "https://app.test/login" });
|
|
37
|
+
assert.ok(result.html.includes("https://app.test/login"));
|
|
38
|
+
assert.ok(result.text.includes("https://app.test/login"));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await t.test("omits sign-in button when no login URL", () => {
|
|
42
|
+
const result = welcomeEmail({});
|
|
43
|
+
assert.ok(!result.html.includes("Sign In"));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await t.test("escapes HTML in user name", () => {
|
|
47
|
+
const result = welcomeEmail({ name: "<script>alert('xss')</script>" });
|
|
48
|
+
assert.ok(!result.html.includes("<script>"));
|
|
49
|
+
assert.ok(result.html.includes("<script>"));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await t.test("wraps content in html layout", () => {
|
|
53
|
+
const result = welcomeEmail({});
|
|
54
|
+
assert.ok(result.html.includes("<!DOCTYPE html>"));
|
|
55
|
+
assert.ok(result.html.includes("</html>"));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("passwordResetEmail", async (t) => {
|
|
60
|
+
const validArgs = { resetUrl: "https://app.test/reset?token=abc123" };
|
|
61
|
+
|
|
62
|
+
await t.test("returns subject, html, and text", () => {
|
|
63
|
+
const result = passwordResetEmail(validArgs);
|
|
64
|
+
assert.ok(result.subject);
|
|
65
|
+
assert.ok(result.html);
|
|
66
|
+
assert.ok(result.text);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await t.test("throws when resetUrl is missing", () => {
|
|
70
|
+
assert.throws(() => passwordResetEmail({}), /resetUrl is required/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await t.test("includes reset URL in html and text", () => {
|
|
74
|
+
const result = passwordResetEmail(validArgs);
|
|
75
|
+
assert.ok(result.html.includes(validArgs.resetUrl));
|
|
76
|
+
assert.ok(result.text.includes(validArgs.resetUrl));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await t.test("uses default app name", () => {
|
|
80
|
+
const result = passwordResetEmail(validArgs);
|
|
81
|
+
assert.ok(result.subject.includes("Quark"));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await t.test("uses custom app name", () => {
|
|
85
|
+
const result = passwordResetEmail({ ...validArgs, appName: "MyApp" });
|
|
86
|
+
assert.ok(result.subject.includes("MyApp"));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await t.test("includes user name when provided", () => {
|
|
90
|
+
const result = passwordResetEmail({ ...validArgs, name: "Bob" });
|
|
91
|
+
assert.ok(result.html.includes("Bob"));
|
|
92
|
+
assert.ok(result.text.includes("Bob"));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await t.test("uses default expiry time", () => {
|
|
96
|
+
const result = passwordResetEmail(validArgs);
|
|
97
|
+
assert.ok(result.html.includes("1 hour"));
|
|
98
|
+
assert.ok(result.text.includes("1 hour"));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await t.test("uses custom expiry time", () => {
|
|
102
|
+
const result = passwordResetEmail({
|
|
103
|
+
...validArgs,
|
|
104
|
+
expiresIn: "30 minutes",
|
|
105
|
+
});
|
|
106
|
+
assert.ok(result.html.includes("30 minutes"));
|
|
107
|
+
assert.ok(result.text.includes("30 minutes"));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await t.test("escapes HTML in user name", () => {
|
|
111
|
+
const result = passwordResetEmail({
|
|
112
|
+
...validArgs,
|
|
113
|
+
name: '<img src=x onerror="alert(1)">',
|
|
114
|
+
});
|
|
115
|
+
assert.ok(!result.html.includes("<img"));
|
|
116
|
+
assert.ok(result.html.includes("<img"));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await t.test("wraps content in html layout", () => {
|
|
120
|
+
const result = passwordResetEmail(validArgs);
|
|
121
|
+
assert.ok(result.html.includes("<!DOCTYPE html>"));
|
|
122
|
+
assert.ok(result.html.includes("</html>"));
|
|
123
|
+
});
|
|
124
|
+
});
|
package/src/email.js
CHANGED
|
@@ -55,13 +55,15 @@ async function createSmtpTransport() {
|
|
|
55
55
|
async function sendViaResend(from, to, subject, html, text) {
|
|
56
56
|
const apiKey = process.env.RESEND_API_KEY;
|
|
57
57
|
if (!apiKey) {
|
|
58
|
-
throw new Error(
|
|
58
|
+
throw new Error(
|
|
59
|
+
"RESEND_API_KEY environment variable is required when EMAIL_PROVIDER=resend",
|
|
60
|
+
);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
const response = await fetch("https://api.resend.com/emails", {
|
|
62
64
|
method: "POST",
|
|
63
65
|
headers: {
|
|
64
|
-
|
|
66
|
+
Authorization: `Bearer ${apiKey}`,
|
|
65
67
|
"Content-Type": "application/json",
|
|
66
68
|
},
|
|
67
69
|
body: JSON.stringify({
|
|
@@ -75,8 +77,12 @@ async function sendViaResend(from, to, subject, html, text) {
|
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
if (!response.ok) {
|
|
78
|
-
const error = await response
|
|
79
|
-
|
|
80
|
+
const error = await response
|
|
81
|
+
.json()
|
|
82
|
+
.catch(() => ({ message: response.statusText }));
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Resend API error: ${error.message || response.statusText}`,
|
|
85
|
+
);
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
return response.json();
|
|
@@ -84,7 +90,7 @@ async function sendViaResend(from, to, subject, html, text) {
|
|
|
84
90
|
|
|
85
91
|
/**
|
|
86
92
|
* Create an email service instance
|
|
87
|
-
*
|
|
93
|
+
*
|
|
88
94
|
* @param {Object} [options]
|
|
89
95
|
* @param {string} [options.provider] - "smtp" or "resend" (defaults to EMAIL_PROVIDER env var or "smtp")
|
|
90
96
|
* @param {string} [options.from] - Sender address (defaults to EMAIL_FROM env var)
|
|
@@ -92,7 +98,8 @@ async function sendViaResend(from, to, subject, html, text) {
|
|
|
92
98
|
*/
|
|
93
99
|
export function createEmailService(options = {}) {
|
|
94
100
|
const provider = options.provider || process.env.EMAIL_PROVIDER || "smtp";
|
|
95
|
-
const from =
|
|
101
|
+
const from =
|
|
102
|
+
options.from || process.env.EMAIL_FROM || "Quark <noreply@localhost>";
|
|
96
103
|
|
|
97
104
|
let smtpTransport = null;
|
|
98
105
|
|
package/src/email.test.js
CHANGED
|
@@ -1,133 +1,146 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
|
-
import {
|
|
2
|
+
import { test } from "node:test";
|
|
3
3
|
import { createEmailService } from "./email.js";
|
|
4
4
|
|
|
5
5
|
test("Email Service", async (t) => {
|
|
6
|
-
await t.test(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
await t.test("SMTP provider: uses Mailhog defaults when no SMTP_HOST set", async () => {
|
|
13
|
-
// Clear any explicit SMTP env vars
|
|
14
|
-
const origHost = process.env.SMTP_HOST;
|
|
15
|
-
const origPort = process.env.SMTP_PORT;
|
|
16
|
-
delete process.env.SMTP_HOST;
|
|
17
|
-
delete process.env.SMTP_PORT;
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const service = createEmailService({ provider: "smtp" });
|
|
21
|
-
|
|
22
|
-
// Mock nodemailer at the transport level
|
|
23
|
-
let capturedConfig = null;
|
|
24
|
-
const mockTransport = {
|
|
25
|
-
sendMail: async (opts) => {
|
|
26
|
-
return { messageId: "test-id-123", ...opts };
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// We test that sendEmail resolves without error
|
|
31
|
-
// The transport is lazily created, so sendEmail triggers creation
|
|
32
|
-
// We can't easily intercept the dynamic import, but we can verify
|
|
33
|
-
// the service was created correctly
|
|
34
|
-
assert.strictEqual(typeof service.sendEmail, "function");
|
|
35
|
-
} finally {
|
|
36
|
-
if (origHost !== undefined) process.env.SMTP_HOST = origHost;
|
|
37
|
-
else delete process.env.SMTP_HOST;
|
|
38
|
-
if (origPort !== undefined) process.env.SMTP_PORT = origPort;
|
|
39
|
-
else delete process.env.SMTP_PORT;
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
await t.test("SMTP provider: uses explicit SMTP_HOST/SMTP_PORT when set", () => {
|
|
44
|
-
const origHost = process.env.SMTP_HOST;
|
|
45
|
-
const origPort = process.env.SMTP_PORT;
|
|
46
|
-
process.env.SMTP_HOST = "mail.example.com";
|
|
47
|
-
process.env.SMTP_PORT = "587";
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const service = createEmailService({ provider: "smtp" });
|
|
6
|
+
await t.test(
|
|
7
|
+
"createEmailService returns object with sendEmail method",
|
|
8
|
+
() => {
|
|
9
|
+
const service = createEmailService();
|
|
10
|
+
assert.strictEqual(typeof service, "object");
|
|
51
11
|
assert.strictEqual(typeof service.sendEmail, "function");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
await t.test(
|
|
16
|
+
"SMTP provider: uses Mailhog defaults when no SMTP_HOST set",
|
|
17
|
+
async () => {
|
|
18
|
+
// Clear any explicit SMTP env vars
|
|
19
|
+
const origHost = process.env.SMTP_HOST;
|
|
20
|
+
const origPort = process.env.SMTP_PORT;
|
|
21
|
+
delete process.env.SMTP_HOST;
|
|
22
|
+
delete process.env.SMTP_PORT;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const service = createEmailService({ provider: "smtp" });
|
|
26
|
+
|
|
27
|
+
// Mock nodemailer at the transport level
|
|
28
|
+
const _capturedConfig = null;
|
|
29
|
+
const _mockTransport = {
|
|
30
|
+
sendMail: async (opts) => {
|
|
31
|
+
return { messageId: "test-id-123", ...opts };
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// We test that sendEmail resolves without error
|
|
36
|
+
// The transport is lazily created, so sendEmail triggers creation
|
|
37
|
+
// We can't easily intercept the dynamic import, but we can verify
|
|
38
|
+
// the service was created correctly
|
|
39
|
+
assert.strictEqual(typeof service.sendEmail, "function");
|
|
40
|
+
} finally {
|
|
41
|
+
if (origHost !== undefined) process.env.SMTP_HOST = origHost;
|
|
42
|
+
else delete process.env.SMTP_HOST;
|
|
43
|
+
if (origPort !== undefined) process.env.SMTP_PORT = origPort;
|
|
44
|
+
else delete process.env.SMTP_PORT;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
await t.test(
|
|
50
|
+
"SMTP provider: uses explicit SMTP_HOST/SMTP_PORT when set",
|
|
51
|
+
() => {
|
|
52
|
+
const origHost = process.env.SMTP_HOST;
|
|
53
|
+
const origPort = process.env.SMTP_PORT;
|
|
54
|
+
process.env.SMTP_HOST = "mail.example.com";
|
|
55
|
+
process.env.SMTP_PORT = "587";
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const service = createEmailService({ provider: "smtp" });
|
|
59
|
+
assert.strictEqual(typeof service.sendEmail, "function");
|
|
60
|
+
} finally {
|
|
61
|
+
if (origHost !== undefined) process.env.SMTP_HOST = origHost;
|
|
62
|
+
else delete process.env.SMTP_HOST;
|
|
63
|
+
if (origPort !== undefined) process.env.SMTP_PORT = origPort;
|
|
64
|
+
else delete process.env.SMTP_PORT;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
await t.test(
|
|
70
|
+
"Resend provider: throws if RESEND_API_KEY missing",
|
|
71
|
+
async () => {
|
|
72
|
+
const origKey = process.env.RESEND_API_KEY;
|
|
73
|
+
delete process.env.RESEND_API_KEY;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const service = createEmailService({ provider: "resend" });
|
|
77
|
+
|
|
78
|
+
await assert.rejects(
|
|
79
|
+
() => service.sendEmail("test@example.com", "Subject", "<p>body</p>"),
|
|
80
|
+
(err) => err instanceof Error && /RESEND_API_KEY/.test(err.message),
|
|
81
|
+
);
|
|
82
|
+
} finally {
|
|
83
|
+
if (origKey !== undefined) process.env.RESEND_API_KEY = origKey;
|
|
84
|
+
else delete process.env.RESEND_API_KEY;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
await t.test(
|
|
90
|
+
"Resend provider: calls fetch with correct URL, headers, and body",
|
|
91
|
+
async () => {
|
|
92
|
+
const origKey = process.env.RESEND_API_KEY;
|
|
93
|
+
process.env.RESEND_API_KEY = "re_test_key_123";
|
|
94
|
+
|
|
95
|
+
// Mock global fetch
|
|
96
|
+
const originalFetch = globalThis.fetch;
|
|
97
|
+
let capturedUrl = null;
|
|
98
|
+
let capturedOptions = null;
|
|
99
|
+
|
|
100
|
+
globalThis.fetch = async (url, opts) => {
|
|
101
|
+
capturedUrl = url;
|
|
102
|
+
capturedOptions = opts;
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
json: async () => ({ id: "resend-msg-id" }),
|
|
106
|
+
};
|
|
94
107
|
};
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const service = createEmailService({
|
|
99
|
-
provider: "resend",
|
|
100
|
-
from: "Test <test@example.com>",
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const result = await service.sendEmail(
|
|
104
|
-
"recipient@example.com",
|
|
105
|
-
"Test Subject",
|
|
106
|
-
"<p>Hello</p>",
|
|
107
|
-
"Hello",
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
assert.strictEqual(capturedUrl, "https://api.resend.com/emails");
|
|
111
|
-
assert.strictEqual(capturedOptions.method, "POST");
|
|
112
|
-
|
|
113
|
-
const headers = capturedOptions.headers;
|
|
114
|
-
assert.strictEqual(headers.Authorization, "Bearer re_test_key_123");
|
|
115
|
-
assert.strictEqual(headers["Content-Type"], "application/json");
|
|
116
108
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
109
|
+
try {
|
|
110
|
+
const service = createEmailService({
|
|
111
|
+
provider: "resend",
|
|
112
|
+
from: "Test <test@example.com>",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = await service.sendEmail(
|
|
116
|
+
"recipient@example.com",
|
|
117
|
+
"Test Subject",
|
|
118
|
+
"<p>Hello</p>",
|
|
119
|
+
"Hello",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
assert.strictEqual(capturedUrl, "https://api.resend.com/emails");
|
|
123
|
+
assert.strictEqual(capturedOptions.method, "POST");
|
|
124
|
+
|
|
125
|
+
const headers = capturedOptions.headers;
|
|
126
|
+
assert.strictEqual(headers.Authorization, "Bearer re_test_key_123");
|
|
127
|
+
assert.strictEqual(headers["Content-Type"], "application/json");
|
|
128
|
+
|
|
129
|
+
const body = JSON.parse(capturedOptions.body);
|
|
130
|
+
assert.strictEqual(body.from, "Test <test@example.com>");
|
|
131
|
+
assert.deepStrictEqual(body.to, ["recipient@example.com"]);
|
|
132
|
+
assert.strictEqual(body.subject, "Test Subject");
|
|
133
|
+
assert.strictEqual(body.html, "<p>Hello</p>");
|
|
134
|
+
assert.strictEqual(body.text, "Hello");
|
|
135
|
+
|
|
136
|
+
assert.strictEqual(result.id, "resend-msg-id");
|
|
137
|
+
} finally {
|
|
138
|
+
globalThis.fetch = originalFetch;
|
|
139
|
+
if (origKey !== undefined) process.env.RESEND_API_KEY = origKey;
|
|
140
|
+
else delete process.env.RESEND_API_KEY;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
);
|
|
131
144
|
|
|
132
145
|
await t.test("Resend provider: throws on non-ok response", async () => {
|
|
133
146
|
const origKey = process.env.RESEND_API_KEY;
|
|
@@ -196,8 +209,7 @@ test("Email Service", async (t) => {
|
|
|
196
209
|
// The service should be created without error (defaults to smtp)
|
|
197
210
|
assert.strictEqual(typeof service.sendEmail, "function");
|
|
198
211
|
} finally {
|
|
199
|
-
if (origProvider !== undefined)
|
|
200
|
-
process.env.EMAIL_PROVIDER = origProvider;
|
|
212
|
+
if (origProvider !== undefined) process.env.EMAIL_PROVIDER = origProvider;
|
|
201
213
|
else delete process.env.EMAIL_PROVIDER;
|
|
202
214
|
}
|
|
203
215
|
});
|
|
@@ -209,7 +221,7 @@ test("Email Service", async (t) => {
|
|
|
209
221
|
const originalFetch = globalThis.fetch;
|
|
210
222
|
let capturedBody = null;
|
|
211
223
|
|
|
212
|
-
globalThis.fetch = async (
|
|
224
|
+
globalThis.fetch = async (_url, opts) => {
|
|
213
225
|
capturedBody = JSON.parse(opts.body);
|
|
214
226
|
return { ok: true, json: async () => ({ id: "msg-1" }) };
|
|
215
227
|
};
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,10 @@ export * from "./authorization.js";
|
|
|
7
7
|
export * from "./cache.js";
|
|
8
8
|
// CSRF protection exports
|
|
9
9
|
export * from "./csrf.js";
|
|
10
|
+
// Email service exports
|
|
11
|
+
export * from "./email.js";
|
|
12
|
+
// Email template exports
|
|
13
|
+
export * from "./email-templates.js";
|
|
10
14
|
// Error Reporter exports
|
|
11
15
|
export * from "./error-reporter.js";
|
|
12
16
|
// Error exports
|
|
@@ -15,8 +19,6 @@ export * from "./errors.js";
|
|
|
15
19
|
export * from "./logger.js";
|
|
16
20
|
// Mailhog exports
|
|
17
21
|
export * from "./mailhog.js";
|
|
18
|
-
// Email service exports
|
|
19
|
-
export * from "./email.js";
|
|
20
22
|
// Queue exports
|
|
21
23
|
export * from "./queue/index.js";
|
|
22
24
|
// Rate limiting exports
|
package/src/mailhog.js
CHANGED
|
@@ -30,8 +30,8 @@ function getMailhogUiUrl() {
|
|
|
30
30
|
export const getMailhogSmtpConfig = () => {
|
|
31
31
|
const url = getMailhogSmtpUrl();
|
|
32
32
|
const match = url.match(/smtp:\/\/([^:]+):(\d+)/);
|
|
33
|
-
const host = match ? match[1] :
|
|
34
|
-
const port = match ? match[2] :
|
|
33
|
+
const host = match ? match[1] : process.env.MAILHOG_HOST || "localhost";
|
|
34
|
+
const port = match ? match[2] : process.env.MAILHOG_SMTP_PORT || "1025";
|
|
35
35
|
|
|
36
36
|
return {
|
|
37
37
|
host,
|
package/src/rate-limiter.js
CHANGED
package/.turbo/turbo-lint.log
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> @quark/core@1.0.0 lint /Users/david/Projects/quark/packages/core
|
|
4
|
-
> biome format --write && biome check --write
|
|
5
|
-
|
|
6
|
-
[0m[34mFormatted [0m[0m[34m17[0m[0m[34m [0m[0m[34mfiles[0m[0m[34m in [0m[0m[34m40[0m[0m[2m[34mms[0m[0m[34m.[0m[0m[34m No fixes applied.[0m
|
|
7
|
-
[0m[34mChecked [0m[0m[34m17[0m[0m[34m [0m[0m[34mfiles[0m[0m[34m in [0m[0m[34m64[0m[0m[2m[34mms[0m[0m[34m.[0m[0m[34m No fixes applied.[0m
|