toiljs 0.0.96 → 0.0.98
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/CHANGELOG.md +10 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +6 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/toil-docs.generated.js +6 -3
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/email/config.js +13 -7
- package/build/devserver/email/providers.js +1 -1
- package/build/devserver/runtime/host.js +3 -0
- package/docs/README.md +2 -1
- package/docs/frontend/README.md +4 -1
- package/docs/frontend/components.md +286 -0
- package/docs/frontend/navigation.md +296 -0
- package/docs/frontend/routing.md +2 -0
- package/docs/frontend/toil-global.md +216 -0
- package/docs/llms.txt +3 -0
- package/package.json +1 -1
- package/src/cli/index.ts +13 -0
- package/src/compiler/toil-docs.generated.ts +6 -3
- package/src/devserver/email/config.ts +20 -8
- package/src/devserver/email/providers.ts +2 -1
- package/src/devserver/runtime/host.ts +5 -0
- package/test/devserver-email.test.ts +18 -0
|
@@ -80,16 +80,20 @@ export function resolveEmailConfig(
|
|
|
80
80
|
if (!validFrom(from)) {
|
|
81
81
|
return { config: null, warning: 'email `from` is not a valid address (CRLF or no `@`)' };
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
83
|
+
// The API-key requirement is PER PROVIDER: Resend authenticates with a key, and
|
|
84
|
+
// Gmail SMTP needs an app password (also carried in the key), but a plain SMTP
|
|
85
|
+
// relay (a local dev catch-all like Mailpit/MailHog on 127.0.0.1, or an internal
|
|
86
|
+
// open relay) needs NO credential. Requiring a key for plain SMTP silently
|
|
87
|
+
// disabled a perfectly good local-SMTP dev setup.
|
|
90
88
|
let provider: ResolvedProvider;
|
|
91
89
|
let smtp: ResolvedSmtp | undefined;
|
|
92
90
|
if (providerId === 'resend') {
|
|
91
|
+
if (apiKey === undefined) {
|
|
92
|
+
return {
|
|
93
|
+
config: null,
|
|
94
|
+
warning: 'provider `resend` requires TOIL_EMAIL_API_KEY (in .env.secrets)',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
93
97
|
provider = 'resend';
|
|
94
98
|
} else if (providerId === 'gmail' || providerId === 'smtp') {
|
|
95
99
|
provider = 'smtp';
|
|
@@ -101,6 +105,14 @@ export function resolveEmailConfig(
|
|
|
101
105
|
if (!host) {
|
|
102
106
|
return { config: null, warning: 'provider `smtp` requires TOIL_EMAIL_SMTP_HOST' };
|
|
103
107
|
}
|
|
108
|
+
// Gmail always needs an app password; plain SMTP may be auth-less (the send
|
|
109
|
+
// path omits `auth` when the key is empty). See providers.ts sendSmtp.
|
|
110
|
+
if (isGmail && apiKey === undefined) {
|
|
111
|
+
return {
|
|
112
|
+
config: null,
|
|
113
|
+
warning: 'provider `gmail` requires TOIL_EMAIL_API_KEY (a Gmail app password)',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
104
116
|
const port = parseInt0(envOf(reserved, 'SMTP_PORT'), c.smtp?.port ?? 0) || 587;
|
|
105
117
|
const user = envOf(reserved, 'SMTP_USER') ?? c.smtp?.user?.trim() ?? from;
|
|
106
118
|
smtp = { host, port, user };
|
|
@@ -115,7 +127,7 @@ export function resolveEmailConfig(
|
|
|
115
127
|
config: {
|
|
116
128
|
provider,
|
|
117
129
|
from,
|
|
118
|
-
apiKey,
|
|
130
|
+
apiKey: apiKey ?? '',
|
|
119
131
|
maxPerMin: parseInt0(envOf(reserved, 'MAX_PER_MIN'), c.maxPerMin ?? 60),
|
|
120
132
|
maxPerDay: parseInt0(envOf(reserved, 'MAX_PER_DAY'), c.maxPerDay ?? 0),
|
|
121
133
|
maxPerRecipientPerHour: parseInt0(
|
|
@@ -91,7 +91,8 @@ export async function sendSmtp(
|
|
|
91
91
|
host: smtp.host,
|
|
92
92
|
port: smtp.port,
|
|
93
93
|
secure: smtp.port === 465, // 465 = implicit TLS; else STARTTLS
|
|
94
|
-
|
|
94
|
+
// Auth only when there is a credential; a local/auth-less relay takes none.
|
|
95
|
+
auth: cfg.apiKey.length > 0 ? { user: smtp.user, pass: cfg.apiKey } : undefined,
|
|
95
96
|
connectionTimeout: POST_TIMEOUT_MS,
|
|
96
97
|
greetingTimeout: POST_TIMEOUT_MS,
|
|
97
98
|
});
|
|
@@ -228,6 +228,11 @@ function devEmailSend(ref: MemoryRef, reqPtr: number, reqLen: number): number {
|
|
|
228
228
|
const { status, parsed } = svc.prepare(raw);
|
|
229
229
|
if (parsed === null) {
|
|
230
230
|
process.stdout.write(` ✉ dev email_send -> ${EmailStatus[status]}\n`);
|
|
231
|
+
// The real send was skipped (bad recipient / deduped / budget / cap), but in
|
|
232
|
+
// DEV still draw the email so its link/code stays testable. Not recorded to
|
|
233
|
+
// the test seam (that tracks accepted sends only).
|
|
234
|
+
const skipped = parseEmailBlob(raw);
|
|
235
|
+
if (skipped !== null) previewDevEmail(skipped, EmailStatus[status]);
|
|
231
236
|
return status;
|
|
232
237
|
}
|
|
233
238
|
recordSentEmail(parsed); // dev test seam
|
|
@@ -154,6 +154,24 @@ describe('config resolution', () => {
|
|
|
154
154
|
resolveEmailConfig({ from: 'a@b.com' }, reserved({ TOIL_EMAIL_API_KEY: 'k', TOIL_EMAIL_PROVIDER: 'mailgun' }))
|
|
155
155
|
.warning,
|
|
156
156
|
).toMatch(/unknown/);
|
|
157
|
+
// gmail still needs an app password
|
|
158
|
+
expect(
|
|
159
|
+
resolveEmailConfig({ provider: 'gmail', from: 'me@gmail.com' }, reserved({})).warning,
|
|
160
|
+
).toMatch(/gmail/);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('plain SMTP needs NO api key (a local / auth-less relay)', () => {
|
|
164
|
+
const { config, warning } = resolveEmailConfig(
|
|
165
|
+
{ provider: 'smtp', from: 'dev@dacely.local' },
|
|
166
|
+
reserved({ TOIL_EMAIL_SMTP_HOST: '127.0.0.1' }),
|
|
167
|
+
);
|
|
168
|
+
expect(warning).toBeNull();
|
|
169
|
+
expect(config).toMatchObject({
|
|
170
|
+
provider: 'smtp',
|
|
171
|
+
from: 'dev@dacely.local',
|
|
172
|
+
apiKey: '', // no credential -> the send path omits SMTP auth
|
|
173
|
+
smtp: { host: '127.0.0.1', port: 587 },
|
|
174
|
+
});
|
|
157
175
|
});
|
|
158
176
|
|
|
159
177
|
it('TOIL_EMAIL_ENABLED=false disables silently', () => {
|