dkimkit 0.0.0-stage → 0.7.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.
Files changed (132) hide show
  1. package/README.md +161 -2
  2. package/dist/src/cli.d.ts +2 -0
  3. package/dist/src/cli.js +398 -0
  4. package/dist/src/cli.js.map +1 -0
  5. package/dist/src/dns-host.d.ts +10 -0
  6. package/dist/src/dns-host.js +31 -0
  7. package/dist/src/dns-host.js.map +1 -0
  8. package/dist/src/errors.d.ts +17 -0
  9. package/dist/src/errors.js +51 -0
  10. package/dist/src/errors.js.map +1 -0
  11. package/dist/src/hosted/api.d.ts +107 -0
  12. package/dist/src/hosted/api.js +919 -0
  13. package/dist/src/hosted/api.js.map +1 -0
  14. package/dist/src/hosted/auth.d.ts +80 -0
  15. package/dist/src/hosted/auth.js +147 -0
  16. package/dist/src/hosted/auth.js.map +1 -0
  17. package/dist/src/hosted/billing-dodo.d.ts +58 -0
  18. package/dist/src/hosted/billing-dodo.js +139 -0
  19. package/dist/src/hosted/billing-dodo.js.map +1 -0
  20. package/dist/src/hosted/billing-stripe.d.ts +82 -0
  21. package/dist/src/hosted/billing-stripe.js +205 -0
  22. package/dist/src/hosted/billing-stripe.js.map +1 -0
  23. package/dist/src/hosted/billing.d.ts +114 -0
  24. package/dist/src/hosted/billing.js +173 -0
  25. package/dist/src/hosted/billing.js.map +1 -0
  26. package/dist/src/hosted/bulk.d.ts +47 -0
  27. package/dist/src/hosted/bulk.js +66 -0
  28. package/dist/src/hosted/bulk.js.map +1 -0
  29. package/dist/src/hosted/cli.d.ts +2 -0
  30. package/dist/src/hosted/cli.js +318 -0
  31. package/dist/src/hosted/cli.js.map +1 -0
  32. package/dist/src/hosted/crypto.d.ts +22 -0
  33. package/dist/src/hosted/crypto.js +55 -0
  34. package/dist/src/hosted/crypto.js.map +1 -0
  35. package/dist/src/hosted/index.d.ts +7 -0
  36. package/dist/src/hosted/index.js +11 -0
  37. package/dist/src/hosted/index.js.map +1 -0
  38. package/dist/src/hosted/markdown.d.ts +16 -0
  39. package/dist/src/hosted/markdown.js +168 -0
  40. package/dist/src/hosted/markdown.js.map +1 -0
  41. package/dist/src/hosted/net.d.ts +9 -0
  42. package/dist/src/hosted/net.js +60 -0
  43. package/dist/src/hosted/net.js.map +1 -0
  44. package/dist/src/hosted/postgres-store.d.ts +62 -0
  45. package/dist/src/hosted/postgres-store.js +387 -0
  46. package/dist/src/hosted/postgres-store.js.map +1 -0
  47. package/dist/src/hosted/serialize.d.ts +144 -0
  48. package/dist/src/hosted/serialize.js +126 -0
  49. package/dist/src/hosted/serialize.js.map +1 -0
  50. package/dist/src/hosted/service.d.ts +170 -0
  51. package/dist/src/hosted/service.js +603 -0
  52. package/dist/src/hosted/service.js.map +1 -0
  53. package/dist/src/hosted/site.d.ts +84 -0
  54. package/dist/src/hosted/site.js +653 -0
  55. package/dist/src/hosted/site.js.map +1 -0
  56. package/dist/src/hosted/sqlite-store.d.ts +45 -0
  57. package/dist/src/hosted/sqlite-store.js +351 -0
  58. package/dist/src/hosted/sqlite-store.js.map +1 -0
  59. package/dist/src/hosted/store.d.ts +257 -0
  60. package/dist/src/hosted/store.js +181 -0
  61. package/dist/src/hosted/store.js.map +1 -0
  62. package/dist/src/hosted/widget.d.ts +46 -0
  63. package/dist/src/hosted/widget.js +130 -0
  64. package/dist/src/hosted/widget.js.map +1 -0
  65. package/dist/src/hosted/worker.d.ts +45 -0
  66. package/dist/src/hosted/worker.js +142 -0
  67. package/dist/src/hosted/worker.js.map +1 -0
  68. package/dist/src/http.d.ts +14 -0
  69. package/dist/src/http.js +54 -0
  70. package/dist/src/http.js.map +1 -0
  71. package/dist/src/index.d.ts +19 -0
  72. package/dist/src/index.js +19 -0
  73. package/dist/src/index.js.map +1 -0
  74. package/dist/src/orchestrator.d.ts +70 -0
  75. package/dist/src/orchestrator.js +69 -0
  76. package/dist/src/orchestrator.js.map +1 -0
  77. package/dist/src/propagation.d.ts +30 -0
  78. package/dist/src/propagation.js +76 -0
  79. package/dist/src/propagation.js.map +1 -0
  80. package/dist/src/providers/dns/aws.d.ts +28 -0
  81. package/dist/src/providers/dns/aws.js +103 -0
  82. package/dist/src/providers/dns/aws.js.map +1 -0
  83. package/dist/src/providers/dns/cloudflare.d.ts +26 -0
  84. package/dist/src/providers/dns/cloudflare.js +97 -0
  85. package/dist/src/providers/dns/cloudflare.js.map +1 -0
  86. package/dist/src/providers/dns/manual.d.ts +12 -0
  87. package/dist/src/providers/dns/manual.js +18 -0
  88. package/dist/src/providers/dns/manual.js.map +1 -0
  89. package/dist/src/providers/dns/route53.d.ts +34 -0
  90. package/dist/src/providers/dns/route53.js +176 -0
  91. package/dist/src/providers/dns/route53.js.map +1 -0
  92. package/dist/src/providers/email/factory.d.ts +29 -0
  93. package/dist/src/providers/email/factory.js +67 -0
  94. package/dist/src/providers/email/factory.js.map +1 -0
  95. package/dist/src/providers/email/mailgun.d.ts +36 -0
  96. package/dist/src/providers/email/mailgun.js +141 -0
  97. package/dist/src/providers/email/mailgun.js.map +1 -0
  98. package/dist/src/providers/email/postmark.d.ts +37 -0
  99. package/dist/src/providers/email/postmark.js +121 -0
  100. package/dist/src/providers/email/postmark.js.map +1 -0
  101. package/dist/src/providers/email/sendgrid.d.ts +31 -0
  102. package/dist/src/providers/email/sendgrid.js +109 -0
  103. package/dist/src/providers/email/sendgrid.js.map +1 -0
  104. package/dist/src/providers/email/ses.d.ts +39 -0
  105. package/dist/src/providers/email/ses.js +123 -0
  106. package/dist/src/providers/email/ses.js.map +1 -0
  107. package/dist/src/providers/factory.d.ts +14 -0
  108. package/dist/src/providers/factory.js +32 -0
  109. package/dist/src/providers/factory.js.map +1 -0
  110. package/dist/src/records.d.ts +35 -0
  111. package/dist/src/records.js +109 -0
  112. package/dist/src/records.js.map +1 -0
  113. package/dist/src/server.d.ts +67 -0
  114. package/dist/src/server.js +301 -0
  115. package/dist/src/server.js.map +1 -0
  116. package/dist/src/types.d.ts +75 -0
  117. package/dist/src/types.js +2 -0
  118. package/dist/src/types.js.map +1 -0
  119. package/dist/src/xml.d.ts +6 -0
  120. package/dist/src/xml.js +23 -0
  121. package/dist/src/xml.js.map +1 -0
  122. package/docs/billing.md +43 -0
  123. package/docs/getting-started.md +66 -0
  124. package/docs/hosted-api.md +192 -0
  125. package/docs/library.md +253 -0
  126. package/docs/self-hosting.md +153 -0
  127. package/docs/widget.md +121 -0
  128. package/package.json +63 -4
  129. package/web/dashboard.html +669 -0
  130. package/web/index.html +697 -0
  131. package/web/widget-frame.html +480 -0
  132. package/web/widget.js +91 -0
@@ -0,0 +1,318 @@
1
+ #!/usr/bin/env node
2
+ import { randomBytes } from 'node:crypto';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { parseArgs } from 'node:util';
6
+ // node:sqlite warns that it is experimental every time it loads; that's expected here.
7
+ const emitWarning = process.emitWarning.bind(process);
8
+ process.emitWarning = ((warning, ...rest) => {
9
+ if (String(warning).includes('SQLite is an experimental feature'))
10
+ return;
11
+ emitWarning(warning, ...rest);
12
+ });
13
+ const HELP = `dkimkit-api — hosted multi-tenant API
14
+
15
+ Usage
16
+ dkimkit-api serve
17
+ dkimkit-api create-account --name "Acme Inc" [--with-sendgrid-key]
18
+ dkimkit-api set-domain-limit --account acct_… (--limit 25 | --unlimited)
19
+ dkimkit-api usage-report [--from 2026-10-01] [--to 2026-10-31] [--format csv|json]
20
+ dkimkit-api stripe-setup Create the Stripe meter, price and webhook for pay-as-you-go
21
+ dkimkit-api generate-encryption-key
22
+
23
+ Environment
24
+ ENCRYPTION_KEY 32 bytes, base64 or hex. Encrypts stored credentials. Required.
25
+ DATABASE_URL postgres://user:pass@host/db — use Postgres (recommended for production)
26
+ DATABASE_PATH Otherwise: SQLite file (default ./data/dkimkit.db)
27
+ PORT, HOST Listen address (default 8080, 0.0.0.0)
28
+ WORKER_CONCURRENCY Domains checked in parallel (default 4)
29
+ RUN_WORKERS Set to "false" to serve the API without background jobs
30
+ ALLOW_PRIVATE_WEBHOOKS "true" allows http:// and private-network webhook URLs (development only)
31
+ PUBLIC_URL Public base URL (https://…). Required for sign-up and billing; used in widget links
32
+ SENDGRID_API_KEY With create-account --with-sendgrid-key: the account's SendGrid key
33
+
34
+ Self-serve sign-up and dashboard (on when PUBLIC_URL is set; SIGNUP_ENABLED=false to turn off)
35
+ EMAIL_FROM Sender address for sign-in emails, e.g. login@yourapp.com
36
+ MAIL_RESEND_API_KEY Resend key used to send them, or:
37
+ MAIL_SENDGRID_API_KEY SendGrid key used to send them (without EMAIL_FROM and a key, links are logged instead)
38
+ APP_NAME Product name on the website, dashboard and emails (default "DKIMkit")
39
+
40
+ Website (served at / when PUBLIC_URL is set: landing, /pricing, /docs, /about, /contact, /feedback, /terms, /privacy, /refunds)
41
+ LEGAL_NAME Who operates the service (your name or business), on About and legal pages
42
+ CONTACT_EMAIL Support and legal contact; feedback form messages go here
43
+ LEGAL_COUNTRY Country whose law governs the terms, e.g. India
44
+ LEGAL_EFFECTIVE_DATE "Last updated" date on legal pages, YYYY-MM-DD
45
+
46
+ Billing (pay as you go: first FREE_DOMAINS free, then PRICE_PER_DOMAIN_CENTS per active domain/month)
47
+ BILLING_PROVIDER dodo or stripe; unset = no billing, no free-tier cap
48
+ FREE_DOMAINS Default 3 (must match the provider's free threshold). Without billing,
49
+ setting it caps free accounts and shows paid plans as coming soon
50
+ PRICE_PER_DOMAIN_CENTS Default 200 (for display; the provider's price is what's charged)
51
+ CURRENCY Default usd
52
+ Dodo Payments: DODO_API_KEY, DODO_WEBHOOK_SECRET, DODO_PRODUCT_ID,
53
+ DODO_MODE (test | live, default test), DODO_METER_EVENT (default active_domains)
54
+ Stripe: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_ID, STRIPE_METER_EVENT
55
+ Webhook endpoint for either: <PUBLIC_URL>/billing/webhook`;
56
+ const log = (level, message, fields) => {
57
+ const line = JSON.stringify({ time: new Date().toISOString(), level, msg: message, ...fields });
58
+ (level === 'error' ? process.stderr : process.stdout).write(line + '\n');
59
+ };
60
+ /** Billing from BILLING_PROVIDER (dodo | stripe) and that provider's variables; undefined when unset. */
61
+ async function billingFromEnv() {
62
+ const provider = process.env.BILLING_PROVIDER;
63
+ if (!provider)
64
+ return undefined;
65
+ const plan = {
66
+ freeDomains: intEnv('FREE_DOMAINS', 3),
67
+ unitAmount: intEnv('PRICE_PER_DOMAIN_CENTS', 200),
68
+ currency: process.env.CURRENCY,
69
+ };
70
+ if (provider === 'dodo') {
71
+ const mode = process.env.DODO_MODE ?? 'test';
72
+ if (mode !== 'test' && mode !== 'live')
73
+ throw new Error('DODO_MODE must be test or live.');
74
+ const { DodoBillingProvider } = await import('./billing-dodo.js');
75
+ return {
76
+ ...plan,
77
+ provider: new DodoBillingProvider({
78
+ apiKey: requireEnv('DODO_API_KEY'),
79
+ webhookSecret: requireEnv('DODO_WEBHOOK_SECRET'),
80
+ productId: requireEnv('DODO_PRODUCT_ID'),
81
+ meterEventName: process.env.DODO_METER_EVENT,
82
+ mode,
83
+ }),
84
+ };
85
+ }
86
+ if (provider === 'stripe') {
87
+ const { StripeBillingProvider } = await import('./billing-stripe.js');
88
+ return {
89
+ ...plan,
90
+ provider: new StripeBillingProvider({
91
+ secretKey: requireEnv('STRIPE_SECRET_KEY'),
92
+ webhookSecret: requireEnv('STRIPE_WEBHOOK_SECRET'),
93
+ priceId: requireEnv('STRIPE_PRICE_ID'),
94
+ meterEventName: process.env.STRIPE_METER_EVENT,
95
+ }),
96
+ };
97
+ }
98
+ throw new Error('BILLING_PROVIDER must be dodo or stripe.');
99
+ }
100
+ function intEnv(name, fallback) {
101
+ const raw = process.env[name];
102
+ if (raw === undefined || raw === '')
103
+ return fallback;
104
+ const n = Number(raw);
105
+ if (!Number.isInteger(n) || n < 0)
106
+ throw new Error(`${name} must be a whole number.`);
107
+ return n;
108
+ }
109
+ function requireEnv(name) {
110
+ const value = process.env[name];
111
+ if (!value)
112
+ throw new Error(`${name} is not set. See --help.`);
113
+ return value;
114
+ }
115
+ /** DATABASE_URL (Postgres) if set, else a SQLite file at DATABASE_PATH. */
116
+ function database() {
117
+ if (process.env.DATABASE_URL)
118
+ return process.env.DATABASE_URL;
119
+ const path = resolve(process.env.DATABASE_PATH ?? './data/dkimkit.db');
120
+ mkdirSync(dirname(path), { recursive: true });
121
+ return path;
122
+ }
123
+ async function main() {
124
+ const { values, positionals } = parseArgs({
125
+ allowPositionals: true,
126
+ options: {
127
+ name: { type: 'string' },
128
+ 'with-sendgrid-key': { type: 'boolean', default: false },
129
+ account: { type: 'string' },
130
+ limit: { type: 'string' },
131
+ unlimited: { type: 'boolean', default: false },
132
+ from: { type: 'string' },
133
+ to: { type: 'string' },
134
+ format: { type: 'string', default: 'csv' },
135
+ help: { type: 'boolean', short: 'h', default: false },
136
+ },
137
+ });
138
+ const command = positionals[0];
139
+ if (values.help || !command) {
140
+ process.stdout.write(HELP + '\n');
141
+ return;
142
+ }
143
+ switch (command) {
144
+ case 'generate-encryption-key':
145
+ process.stdout.write(randomBytes(32).toString('base64') + '\n');
146
+ return;
147
+ case 'create-account': {
148
+ if (!values.name)
149
+ throw new Error('Pass --name.');
150
+ const sendgridKey = values['with-sendgrid-key'] ? requireEnv('SENDGRID_API_KEY') : undefined;
151
+ const { openStore } = await import('./store.js');
152
+ const { DomainService } = await import('./service.js');
153
+ const { Vault } = await import('./crypto.js');
154
+ const vault = new Vault(requireEnv('ENCRYPTION_KEY'));
155
+ const store = await openStore(database());
156
+ try {
157
+ const service = new DomainService({ store, vault });
158
+ const { account, apiKey } = await service.createAccount(values.name, sendgridKey);
159
+ process.stdout.write(`Account: ${account.id} (${account.name})\nAPI key: ${apiKey}\n\n` +
160
+ `Store the API key now; only its hash is kept.\n` +
161
+ (sendgridKey ? '' : `Next: PATCH /v1/account with {"sendgrid_api_key": "SG..."}\n`));
162
+ }
163
+ finally {
164
+ await store.close();
165
+ }
166
+ return;
167
+ }
168
+ case 'serve': {
169
+ const { startHostedApi } = await import('./api.js');
170
+ const { ResendMailer, SendGridMailer } = await import('./auth.js');
171
+ const publicUrl = process.env.PUBLIC_URL;
172
+ const signupEnabled = Boolean(publicUrl) && process.env.SIGNUP_ENABLED !== 'false';
173
+ const from = process.env.EMAIL_FROM;
174
+ const resendKey = process.env.MAIL_RESEND_API_KEY;
175
+ const sendgridKey = process.env.MAIL_SENDGRID_API_KEY;
176
+ const fromName = process.env.APP_NAME;
177
+ const mailer = !from
178
+ ? undefined
179
+ : resendKey
180
+ ? new ResendMailer({ apiKey: resendKey, from, fromName })
181
+ : sendgridKey
182
+ ? new SendGridMailer({ apiKey: sendgridKey, from, fromName })
183
+ : undefined;
184
+ if (signupEnabled && !mailer)
185
+ log('warn', 'EMAIL_FROM and MAIL_RESEND_API_KEY or MAIL_SENDGRID_API_KEY not set: sign-in links will be logged, not emailed');
186
+ const billing = await billingFromEnv();
187
+ const api = await startHostedApi({
188
+ signup: signupEnabled ? { mailer, appName: process.env.APP_NAME } : undefined,
189
+ billing,
190
+ // Before payments launch, FREE_DOMAINS alone sets the free plan's cap.
191
+ freePlan: !billing && process.env.FREE_DOMAINS ? { freeDomains: intEnv('FREE_DOMAINS', 3) } : undefined,
192
+ site: publicUrl
193
+ ? {
194
+ appName: process.env.APP_NAME,
195
+ legalName: process.env.LEGAL_NAME,
196
+ contactEmail: process.env.CONTACT_EMAIL,
197
+ legalCountry: process.env.LEGAL_COUNTRY,
198
+ effectiveDate: process.env.LEGAL_EFFECTIVE_DATE,
199
+ mailProvider: mailer instanceof ResendMailer ? 'Resend' : mailer ? 'Twilio SendGrid' : undefined,
200
+ }
201
+ : undefined,
202
+ database: database(),
203
+ encryptionKey: requireEnv('ENCRYPTION_KEY'),
204
+ port: Number(process.env.PORT ?? 8080),
205
+ host: process.env.HOST ?? '0.0.0.0',
206
+ workers: process.env.RUN_WORKERS !== 'false',
207
+ runner: { concurrency: Number(process.env.WORKER_CONCURRENCY ?? 4) },
208
+ allowPrivateWebhooks: process.env.ALLOW_PRIVATE_WEBHOOKS === 'true',
209
+ publicUrl: process.env.PUBLIC_URL,
210
+ log,
211
+ });
212
+ const missing = api.site?.missingSettings() ?? [];
213
+ if (missing.length)
214
+ log('warn', `website shows placeholders until these are set: ${missing.join(', ')}`);
215
+ log('info', 'listening', {
216
+ url: api.url,
217
+ workers: process.env.RUN_WORKERS !== 'false',
218
+ database: process.env.DATABASE_URL ? 'postgres' : 'sqlite',
219
+ signup: signupEnabled,
220
+ billing: billing ? `${billing.provider.name}${process.env.DODO_MODE === 'live' || billing.provider.name !== 'dodo' ? '' : ' (test mode)'}` : false,
221
+ });
222
+ const shutdown = async (signal) => {
223
+ log('info', 'shutting down', { signal });
224
+ await api.close();
225
+ process.exit(0);
226
+ };
227
+ process.once('SIGTERM', () => void shutdown('SIGTERM'));
228
+ process.once('SIGINT', () => void shutdown('SIGINT'));
229
+ return;
230
+ }
231
+ case 'stripe-setup': {
232
+ const { StripeClient, setupStripe } = await import('./billing-stripe.js');
233
+ const publicUrl = process.env.PUBLIC_URL?.replace(/\/$/, '');
234
+ const result = await setupStripe(new StripeClient(requireEnv('STRIPE_SECRET_KEY')), {
235
+ freeDomains: intEnv('FREE_DOMAINS', 3),
236
+ unitAmount: intEnv('PRICE_PER_DOMAIN_CENTS', 200),
237
+ currency: process.env.CURRENCY ?? 'usd',
238
+ meterEventName: process.env.STRIPE_METER_EVENT ?? 'active_domains',
239
+ webhookUrl: publicUrl ? `${publicUrl}/billing/webhook` : undefined,
240
+ });
241
+ process.stdout.write(`Created meter ${result.meterId}, product ${result.productId} and price ${result.priceId}.\n\n` +
242
+ `Add to your environment:\n STRIPE_PRICE_ID=${result.priceId}\n` +
243
+ (result.webhook
244
+ ? ` STRIPE_WEBHOOK_SECRET=${result.webhook.secret}\n\nWebhook endpoint ${result.webhook.id} sends events to ${publicUrl}/billing/webhook.\n`
245
+ : `\nSet PUBLIC_URL and run again to also create the webhook endpoint, or add ${'<PUBLIC_URL>'}/billing/webhook in the Stripe dashboard\n` +
246
+ `with events checkout.session.completed and customer.subscription.{created,updated,deleted}.\n`) +
247
+ `Also turn on the Customer Portal in Stripe (Settings → Billing → Customer portal).\n`);
248
+ return;
249
+ }
250
+ case 'set-domain-limit': {
251
+ if (!values.account)
252
+ throw new Error('Pass --account.');
253
+ if (values.unlimited === Boolean(values.limit))
254
+ throw new Error('Pass either --limit <n> or --unlimited.');
255
+ const limit = values.unlimited ? null : Number(values.limit);
256
+ if (limit !== null && (!Number.isInteger(limit) || limit < 0))
257
+ throw new Error('--limit must be a whole number.');
258
+ await withService(async (service) => {
259
+ const account = await service.setDomainLimit(values.account, limit);
260
+ process.stdout.write(`${account.id} (${account.name}): domain limit ${account.domainLimit ?? 'unlimited'}\n`);
261
+ });
262
+ return;
263
+ }
264
+ case 'usage-report': {
265
+ if (values.format !== 'csv' && values.format !== 'json')
266
+ throw new Error('--format must be csv or json.');
267
+ const { parseUsagePeriod } = await import('./api.js');
268
+ const query = new URLSearchParams();
269
+ if (values.from)
270
+ query.set('from', values.from);
271
+ if (values.to)
272
+ query.set('to', values.to);
273
+ const { from, to } = parseUsagePeriod(query, Date.now());
274
+ await withService(async (service) => {
275
+ const rows = await service.usageReport(from, to);
276
+ if (values.format === 'json') {
277
+ const accounts = rows.map((r) => ({
278
+ account_id: r.accountId,
279
+ name: r.name,
280
+ active_domains: r.activeDomains,
281
+ active_domain_days: r.activeDomainDays,
282
+ domains_created: r.domainsCreated,
283
+ verifications: r.verifications,
284
+ domain_limit: r.domainLimit,
285
+ }));
286
+ process.stdout.write(JSON.stringify({ from, to, accounts }, null, 2) + '\n');
287
+ return;
288
+ }
289
+ const csv = (v) => (/[",\n]/.test(String(v)) ? `"${String(v).replace(/"/g, '""')}"` : String(v));
290
+ const header = ['from', 'to', 'account_id', 'name', 'active_domains', 'active_domain_days', 'domains_created', 'verifications', 'domain_limit'];
291
+ const lines = rows.map((r) => [from, to, r.accountId, r.name, r.activeDomains, r.activeDomainDays, r.domainsCreated, r.verifications, r.domainLimit ?? ''].map(csv).join(','));
292
+ process.stdout.write([header.join(','), ...lines].join('\n') + '\n');
293
+ });
294
+ return;
295
+ }
296
+ default:
297
+ throw new Error(`Unknown command "${command}". See --help.`);
298
+ }
299
+ }
300
+ /** Runs `fn` with a service on the configured database, closing it afterwards. */
301
+ async function withService(fn) {
302
+ const { openStore } = await import('./store.js');
303
+ const { DomainService } = await import('./service.js');
304
+ const { Vault } = await import('./crypto.js');
305
+ const vault = new Vault(requireEnv('ENCRYPTION_KEY'));
306
+ const store = await openStore(database());
307
+ try {
308
+ await fn(new DomainService({ store, vault }));
309
+ }
310
+ finally {
311
+ await store.close();
312
+ }
313
+ }
314
+ main().catch((err) => {
315
+ process.stderr.write(`✘ ${err instanceof Error ? err.message : String(err)}\n`);
316
+ process.exit(1);
317
+ });
318
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/hosted/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /** AES-256-GCM for secrets at rest. Ciphertexts are versioned so keys can be rotated later. */
2
+ export declare class Vault {
3
+ private readonly key;
4
+ constructor(key: string);
5
+ encrypt(plaintext: string): string;
6
+ decrypt(token: string): string;
7
+ encryptJson(value: unknown): string;
8
+ decryptJson<T>(token: string): T;
9
+ }
10
+ export declare const newId: (prefix: string) => string;
11
+ export declare const newApiKey: () => string;
12
+ export declare const newWebhookSecret: () => string;
13
+ /** API keys are high-entropy, so a plain SHA-256 is enough to store them safely. */
14
+ export declare const hashApiKey: (key: string) => string;
15
+ export declare const WEBHOOK_SIGNATURE_HEADER = "x-dkimkit-signature";
16
+ /** `t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">` */
17
+ export declare function signWebhook(secret: string, body: string, timestampSec: number): string;
18
+ /** For webhook receivers: checks the signature and rejects stale timestamps (replays). */
19
+ export declare function verifyWebhookSignature(secret: string, body: string, header: string | null | undefined, { toleranceSec, nowSec }?: {
20
+ toleranceSec?: number | undefined;
21
+ nowSec?: number | undefined;
22
+ }): boolean;
@@ -0,0 +1,55 @@
1
+ import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { ConfigError } from '../errors.js';
3
+ /** AES-256-GCM for secrets at rest. Ciphertexts are versioned so keys can be rotated later. */
4
+ export class Vault {
5
+ key;
6
+ constructor(key) {
7
+ const decoded = /^[0-9a-f]{64}$/i.test(key) ? Buffer.from(key, 'hex') : Buffer.from(key, 'base64');
8
+ if (decoded.length !== 32) {
9
+ throw new ConfigError('ENCRYPTION_KEY must be 32 bytes, base64 or hex encoded (try: openssl rand -base64 32).');
10
+ }
11
+ this.key = decoded;
12
+ }
13
+ encrypt(plaintext) {
14
+ const iv = randomBytes(12);
15
+ const cipher = createCipheriv('aes-256-gcm', this.key, iv);
16
+ const data = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
17
+ return ['v1', iv, cipher.getAuthTag(), data].map((p) => (typeof p === 'string' ? p : p.toString('base64url'))).join('.');
18
+ }
19
+ decrypt(token) {
20
+ const [version, iv, tag, data] = token.split('.');
21
+ if (version !== 'v1' || !iv || !tag || data === undefined)
22
+ throw new Error('Unrecognized ciphertext format');
23
+ const decipher = createDecipheriv('aes-256-gcm', this.key, Buffer.from(iv, 'base64url'));
24
+ decipher.setAuthTag(Buffer.from(tag, 'base64url'));
25
+ return Buffer.concat([decipher.update(Buffer.from(data, 'base64url')), decipher.final()]).toString('utf8');
26
+ }
27
+ encryptJson(value) {
28
+ return this.encrypt(JSON.stringify(value));
29
+ }
30
+ decryptJson(token) {
31
+ return JSON.parse(this.decrypt(token));
32
+ }
33
+ }
34
+ export const newId = (prefix) => `${prefix}_${randomBytes(12).toString('base64url')}`;
35
+ export const newApiKey = () => `dk_live_${randomBytes(24).toString('base64url')}`;
36
+ export const newWebhookSecret = () => `whsec_${randomBytes(24).toString('base64url')}`;
37
+ /** API keys are high-entropy, so a plain SHA-256 is enough to store them safely. */
38
+ export const hashApiKey = (key) => createHash('sha256').update(key, 'utf8').digest('hex');
39
+ export const WEBHOOK_SIGNATURE_HEADER = 'x-dkimkit-signature';
40
+ /** `t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">` */
41
+ export function signWebhook(secret, body, timestampSec) {
42
+ const mac = createHmac('sha256', secret).update(`${timestampSec}.${body}`, 'utf8').digest('hex');
43
+ return `t=${timestampSec},v1=${mac}`;
44
+ }
45
+ /** For webhook receivers: checks the signature and rejects stale timestamps (replays). */
46
+ export function verifyWebhookSignature(secret, body, header, { toleranceSec = 300, nowSec = Math.floor(Date.now() / 1000) } = {}) {
47
+ const parts = Object.fromEntries((header ?? '').split(',').map((p) => p.trim().split('=')));
48
+ const timestamp = Number(parts.t);
49
+ if (!Number.isInteger(timestamp) || !parts.v1 || Math.abs(nowSec - timestamp) > toleranceSec)
50
+ return false;
51
+ const expected = Buffer.from(signWebhook(secret, body, timestamp).split('v1=')[1], 'hex');
52
+ const given = Buffer.from(parts.v1, 'hex');
53
+ return expected.length === given.length && timingSafeEqual(expected, given);
54
+ }
55
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/hosted/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACrH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,+FAA+F;AAC/F,MAAM,OAAO,KAAK;IACC,GAAG,CAAS;IAE7B,YAAY,GAAW;QACrB,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACnG,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC1B,MAAM,IAAI,WAAW,CAAC,wFAAwF,CAAC,CAAC;QAClH,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC;IACrB,CAAC;IAED,OAAO,CAAC,SAAiB;QACvB,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC/E,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3H,CAAC;IAED,OAAO,CAAC,KAAa;QACnB,MAAM,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QAC7G,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;QACzF,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7G,CAAC;IAED,WAAW,CAAC,KAAc;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,WAAW,CAAI,KAAa;QAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAM,CAAC;IAC9C,CAAC;CACF;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAC9F,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,WAAW,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE,CAAC,SAAS,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAEvF,oFAAoF;AACpF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAElG,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAE9D,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAY,EAAE,YAAoB;IAC5E,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,YAAY,IAAI,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjG,OAAO,KAAK,YAAY,OAAO,GAAG,EAAE,CAAC;AACvC,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,IAAY,EACZ,MAAiC,EACjC,EAAE,YAAY,GAAG,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE;IAEnE,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAqB,CAAC,CAAC,CAAC;IAChH,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY;QAAE,OAAO,KAAK,CAAC;IAC3G,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC;IAC3F,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { startHostedApi, createApiHandler, parseDomainInput, type HostedApi, type HostedApiOptions, type ApiOptions } from './api.js';
2
+ export { DomainService, realProviders, type ProviderFactory, type ServiceOptions, type DnsCredentials, type Logger } from './service.js';
3
+ export { BackgroundRunner, type RunnerOptions } from './worker.js';
4
+ export { WidgetService, type WidgetContext } from './widget.js';
5
+ export { openStore, type Store, type Account, type DomainRow, type DomainStatus, type DomainEvent, type WebhookDelivery, type WidgetSession } from './store.js';
6
+ export { Vault, signWebhook, verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER } from './crypto.js';
7
+ export { toPublicDomain, toPublicEvent, toPublicAccount } from './serialize.js';
@@ -0,0 +1,11 @@
1
+ // Hosted multi-tenant API. Store classes live at their own subpaths
2
+ // (dkimkit/hosted/sqlite, /hosted/postgres) so that importing this
3
+ // never loads node:sqlite or pg unless that backend is used; openStore() picks one by URL.
4
+ export { startHostedApi, createApiHandler, parseDomainInput } from './api.js';
5
+ export { DomainService, realProviders } from './service.js';
6
+ export { BackgroundRunner } from './worker.js';
7
+ export { WidgetService } from './widget.js';
8
+ export { openStore } from './store.js';
9
+ export { Vault, signWebhook, verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER } from './crypto.js';
10
+ export { toPublicDomain, toPublicEvent, toPublicAccount } from './serialize.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hosted/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,mEAAmE;AACnE,2FAA2F;AAC3F,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAA0D,MAAM,UAAU,CAAC;AACtI,OAAO,EAAE,aAAa,EAAE,aAAa,EAA+E,MAAM,cAAc,CAAC;AACzI,OAAO,EAAE,gBAAgB,EAAsB,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,SAAS,EAA2H,MAAM,YAAY,CAAC;AAChK,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface RenderedMarkdown {
2
+ html: string;
3
+ title: string;
4
+ /** h2/h3 headings for an on-page table of contents. */
5
+ headings: {
6
+ level: number;
7
+ text: string;
8
+ id: string;
9
+ }[];
10
+ }
11
+ export interface RenderOptions {
12
+ /** Maps a relative link (e.g. "billing.md#plans") to a URL, or null to render the text without a link. */
13
+ resolveLink?: (href: string) => string | null;
14
+ }
15
+ export declare function slugify(text: string): string;
16
+ export declare function renderMarkdown(source: string, opts?: RenderOptions): RenderedMarkdown;
@@ -0,0 +1,168 @@
1
+ // A small Markdown renderer for our own docs: headings, paragraphs, nested lists
2
+ // (with code blocks inside items), fenced code, tables, links, **bold**, *italic*
3
+ // and `code`. Everything is escaped first, so docs can't inject markup.
4
+ const escapeHtml = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
5
+ export function slugify(text) {
6
+ return text
7
+ .toLowerCase()
8
+ .replace(/<[^>]+>/g, '')
9
+ .replace(/[`*_]/g, '')
10
+ .replace(/[^\p{L}\p{N}\s-]/gu, '')
11
+ .trim()
12
+ .replace(/\s+/g, '-');
13
+ }
14
+ const SAFE_URL = /^(https?:\/\/|mailto:|\/|#)/i;
15
+ function inline(text, opts) {
16
+ // Code spans first, so nothing inside them is formatted.
17
+ const codes = [];
18
+ let s = text.replace(/`([^`]+)`/g, (_, code) => `\u0000${codes.push(code) - 1}\u0000`);
19
+ s = escapeHtml(s);
20
+ // URLs may contain one level of balanced parentheses.
21
+ s = s.replace(/\[([^\]]+)\]\(((?:[^()\s]|\([^()\s]*\))+)\)/g, (_, label, rawHref) => {
22
+ const href = rawHref.replace(/&amp;/g, '&');
23
+ // Relative links, and links into /docs/, go through the resolver (which may drop unpublished pages).
24
+ const resolvable = !SAFE_URL.test(href) || href.startsWith('/docs/');
25
+ const resolved = resolvable ? (opts.resolveLink ? opts.resolveLink(href) : SAFE_URL.test(href) ? href : null) : href;
26
+ if (!resolved || !SAFE_URL.test(resolved))
27
+ return label;
28
+ const external = /^https?:\/\//i.test(resolved);
29
+ return `<a href="${escapeHtml(resolved)}"${external ? ' rel="noopener noreferrer"' : ''}>${label}</a>`;
30
+ });
31
+ s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
32
+ s = s.replace(/(^|[\s(])\*([^*\s][^*]*?)\*(?=[\s).,;:!?]|$)/g, '$1<em>$2</em>');
33
+ return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
34
+ }
35
+ const indentOf = (line) => line.length - line.trimStart().length;
36
+ const LIST_ITEM = /^(\s*)([-*]|\d+\.)\s+(.*)$/;
37
+ /** Splits a table row on unescaped pipes. */
38
+ function cells(row) {
39
+ return row
40
+ .trim()
41
+ .replace(/^\|/, '')
42
+ .replace(/\|$/, '')
43
+ .split(/(?<!\\)\|/)
44
+ .map((c) => c.trim().replace(/\\\|/g, '|'));
45
+ }
46
+ function blocks(lines, opts, out, tight = false) {
47
+ const html = [];
48
+ let i = 0;
49
+ const paragraph = [];
50
+ const flush = () => {
51
+ if (paragraph.length === 0)
52
+ return;
53
+ const content = inline(paragraph.join(' '), opts);
54
+ html.push(tight ? content : `<p>${content}</p>`);
55
+ paragraph.length = 0;
56
+ };
57
+ while (i < lines.length) {
58
+ const line = lines[i];
59
+ const trimmed = line.trim();
60
+ if (trimmed === '') {
61
+ flush();
62
+ i++;
63
+ continue;
64
+ }
65
+ // Fenced code.
66
+ const fence = /^(\s*)```\s*([\w-]*)\s*$/.exec(line);
67
+ if (fence) {
68
+ flush();
69
+ const indent = fence[1].length;
70
+ const code = [];
71
+ i++;
72
+ while (i < lines.length && !/^\s*```\s*$/.test(lines[i])) {
73
+ code.push(lines[i].slice(Math.min(indent, indentOf(lines[i]))));
74
+ i++;
75
+ }
76
+ i++; // closing fence
77
+ const lang = fence[2] ? ` class="language-${escapeHtml(fence[2])}"` : '';
78
+ html.push(`<pre><code${lang}>${escapeHtml(code.join('\n'))}</code></pre>`);
79
+ continue;
80
+ }
81
+ // Headings.
82
+ const heading = /^(#{1,6})\s+(.*)$/.exec(trimmed);
83
+ if (heading) {
84
+ flush();
85
+ const level = heading[1].length;
86
+ const text = heading[2];
87
+ const id = slugify(text);
88
+ const rendered = inline(text, opts);
89
+ if (level === 1 && !out.title)
90
+ out.title = text.replace(/[`*]/g, '');
91
+ if (level === 2 || level === 3)
92
+ out.headings.push({ level, text: rendered, id });
93
+ html.push(`<h${level} id="${escapeHtml(id)}">${rendered}</h${level}>`);
94
+ i++;
95
+ continue;
96
+ }
97
+ // Tables: a pipe row followed by a separator row.
98
+ if (trimmed.startsWith('|') && i + 1 < lines.length && /^\s*\|?\s*:?-{2,}/.test(lines[i + 1])) {
99
+ flush();
100
+ const head = cells(line);
101
+ i += 2;
102
+ const rows = [];
103
+ while (i < lines.length && lines[i].trim().startsWith('|'))
104
+ rows.push(cells(lines[i++]));
105
+ html.push(`<div class="table"><table><thead><tr>${head.map((c) => `<th>${inline(c, opts)}</th>`).join('')}</tr></thead><tbody>` +
106
+ rows.map((r) => `<tr>${head.map((_, k) => `<td>${inline(r[k] ?? '', opts)}</td>`).join('')}</tr>`).join('') +
107
+ '</tbody></table></div>');
108
+ continue;
109
+ }
110
+ // Lists: items at this indent, each owning the more-indented lines after it.
111
+ const item = LIST_ITEM.exec(line);
112
+ if (item) {
113
+ flush();
114
+ const baseIndent = item[1].length;
115
+ const ordered = /\d/.test(item[2][0]);
116
+ const start = ordered ? Number.parseInt(item[2], 10) : 1;
117
+ const items = [];
118
+ while (i < lines.length) {
119
+ const m = LIST_ITEM.exec(lines[i]);
120
+ if (!m || m[1].length !== baseIndent || /\d/.test(m[2][0]) !== ordered)
121
+ break;
122
+ const contentIndent = baseIndent + m[2].length + 1;
123
+ const body = [m[3]];
124
+ i++;
125
+ // Continuation: more-indented lines, and blank lines followed by more-indented lines.
126
+ while (i < lines.length) {
127
+ const next = lines[i];
128
+ if (next.trim() === '') {
129
+ const following = lines.slice(i + 1).find((l) => l.trim() !== '');
130
+ if (following === undefined || indentOf(following) <= baseIndent)
131
+ break;
132
+ body.push('');
133
+ i++;
134
+ continue;
135
+ }
136
+ if (indentOf(next) <= baseIndent)
137
+ break;
138
+ body.push(next.slice(Math.min(contentIndent, indentOf(next))));
139
+ i++;
140
+ }
141
+ const loose = body.some((l) => l.trim() === '');
142
+ items.push(`<li>${blocks(body, opts, out, !loose)}</li>`);
143
+ // Blank line between items of the same list.
144
+ while (i < lines.length && lines[i].trim() === '') {
145
+ const following = lines.slice(i + 1).find((l) => l.trim() !== '');
146
+ const m2 = following && LIST_ITEM.exec(following);
147
+ if (!m2 || m2[1].length !== baseIndent)
148
+ break;
149
+ i++;
150
+ }
151
+ }
152
+ const tag = ordered ? 'ol' : 'ul';
153
+ html.push(`<${tag}${ordered && start !== 1 ? ` start="${start}"` : ''}>${items.join('')}</${tag}>`);
154
+ continue;
155
+ }
156
+ // Paragraph text (a list or other block ends it).
157
+ paragraph.push(trimmed);
158
+ i++;
159
+ }
160
+ flush();
161
+ return html.join(tight ? ' ' : '\n');
162
+ }
163
+ export function renderMarkdown(source, opts = {}) {
164
+ const out = { html: '', title: '', headings: [] };
165
+ out.html = blocks(source.replace(/\r\n/g, '\n').split('\n'), opts, out);
166
+ return out;
167
+ }
168
+ //# sourceMappingURL=markdown.js.map