Compare commits

..

10 commits
0.3.0 ... main

9 changed files with 3919 additions and 4085 deletions

View file

@ -42,3 +42,8 @@ deno install \
# OR # OR
./cohamail <subcommand> -h ./cohamail <subcommand> -h
``` ```
## Configuration
You can override default config and templates with your own in
`/etc/cohabit/mailer/(config|templates)/`.

View file

@ -1,7 +1,8 @@
import { fromFileUrl } from '@std/path' import { exists } from '@std/fs/exists'
import type { JSX } from 'preact' import type { JSX } from 'preact'
import { EnumType } from '@cliffy/command' import { EnumType } from '@cliffy/command'
import type { Template } from '../types.ts' import type { Template } from '../types.ts'
import * as defaultTemplates from '../templates/mod.ts'
export const templates: Map< export const templates: Map<
string, string,
@ -11,25 +12,38 @@ export const templates: Map<
> >
> = new Map() > = new Map()
const templatesDirUrl = import.meta.resolve('../templates') //Load default templates
const templatesDir = fromFileUrl(templatesDirUrl) for (const template of Object.values(defaultTemplates)) {
//@ts-expect-error types are checked at runtime later
templates.set(template.name, template)
}
//Load templates dynamicaly //Load local templates
for await ( const basePath = '/etc/cohabit/mailer/templates'
const template of Deno.readDir(templatesDir)
) { if (await exists(basePath)) {
if ( for await (const template of Deno.readDir(basePath)) {
template.isFile && if (!template.isFile) continue
template.name.endsWith('.tsx') && if (!template.name.endsWith('.tsx')) continue
!template.name.startsWith('_') if (template.name.startsWith('_')) continue
) {
const modPath = new URL( const mod = await import(`${basePath}/${template.name}`).catch(
template.name, checkFsErrors,
`${import.meta.resolve('../templates')}/`,
) )
const mod = await import(modPath.href)
templates.set(mod.default.name, mod.default) templates.set(mod.default.name, mod.default)
} }
} }
function checkFsErrors(error: Error) {
if (error instanceof Deno.errors.PermissionDenied) {
throw new Error(
'unable to load config file due to read access permissions issues',
{
cause: error,
},
)
}
throw error
}
export const templateType = new EnumType([...templates.keys()]) export const templateType = new EnumType([...templates.keys()])

49
config/loader.ts Normal file
View file

@ -0,0 +1,49 @@
import type { JsonValue } from '@std/json'
import defaultAccounts from './account.json' with { type: 'json' }
import defaultDkim from './dkim.json' with { type: 'json' }
function checkFsErrors(error: Error) {
if (error instanceof Deno.errors.NotFound) {
//use default config file
return
}
if (error instanceof Deno.errors.PermissionDenied) {
throw new Error(
'unable to load config file due to read access permissions issues',
{
cause: error,
},
)
}
throw error
}
type JsonRecord = Record<string, JsonValue>
export type DkimRecord = {
domainName: string
keySelector: string
privateKey: string
}
export type AccountRecord = Record<string, {
name: string
address: string
}>
async function readJsonFile<
T extends JsonRecord,
>(path: string, defaultValue: T): Promise<T> {
const file = await Deno.readTextFile(path).catch(checkFsErrors)
const json = JSON.parse(file ?? '{}')
return { ...defaultValue, ...json }
}
const basePath = '/ect/cohabit/mailer/config'
export const accounts = await readJsonFile<AccountRecord>(
`${basePath}/account.json`,
defaultAccounts,
)
export const dkim = await readJsonFile<DkimRecord>(
`${basePath}/dkim.json`,
defaultDkim,
)

View file

@ -1,6 +1,6 @@
{ {
"name": "@cohabit/mailer", "name": "@cohabit/mailer",
"version": "0.3.0", "version": "0.5.2",
"exports": { "exports": {
".": "./mod.ts", ".": "./mod.ts",
"./cli": "./cli.ts", "./cli": "./cli.ts",
@ -8,8 +8,8 @@
"./templates": "./templates/mod.ts" "./templates": "./templates/mod.ts"
}, },
"tasks": { "tasks": {
"compile": "deno compile --allow-read --allow-env --allow-net=0.0.0.0 --allow-sys=osRelease,networkInterfaces --allow-run=/usr/sbin/sendmail,whoami --output=bin/mailer --target=x86_64-unknown-linux-gnu ./cli.ts", "compile": "deno compile --allow-read --allow-env --allow-net=0.0.0.0,jsr.io --allow-sys=osRelease,networkInterfaces --allow-run=/usr/sbin/sendmail,whoami --output=bin/mailer --target=x86_64-unknown-linux-gnu ./cli.ts",
"cli": "deno run --allow-read --allow-env --allow-net=0.0.0.0 --allow-sys=osRelease,networkInterfaces --allow-run=/usr/sbin/sendmail,whoami ./cli.ts" "cli": "deno run --allow-read --allow-env --allow-net=0.0.0.0,jsr.io --allow-sys=osRelease,networkInterfaces --allow-run=/usr/sbin/sendmail,whoami ./cli.ts"
}, },
"fmt": { "fmt": {
"singleQuote": true, "singleQuote": true,
@ -19,7 +19,9 @@
"imports": { "imports": {
"@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.5", "@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.5",
"@cliffy/prompt": "jsr:@cliffy/prompt@^1.0.0-rc.5", "@cliffy/prompt": "jsr:@cliffy/prompt@^1.0.0-rc.5",
"@std/path": "jsr:@std/path@^0.221.0", "@std/fs": "jsr:@std/fs@^1.0.13",
"@std/json": "jsr:@std/json@^1.0.1",
"@types/nodemailer": "npm:@types/nodemailer@^6.4.15",
"jsx-email": "npm:jsx-email@^1.10.12", "jsx-email": "npm:jsx-email@^1.10.12",
"nodemailer": "npm:nodemailer@^6.9.13", "nodemailer": "npm:nodemailer@^6.9.13",
"nodemailer-smime": "npm:nodemailer-smime@^1.1.0", "nodemailer-smime": "npm:nodemailer-smime@^1.1.0",
@ -29,5 +31,6 @@
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact" "jsxImportSource": "preact"
} },
"license": "MIT"
} }

7878
deno.lock

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
import accounts from '../config/account.json' with { type: 'json' } import { accounts, dkim } from '../config/loader.ts'
import dkim from '../config/dkim.json' with { type: 'json' }
export type AddressString = `${string}@${string}.${string}` export type AddressString = `${string}@${string}.${string}`
@ -22,7 +21,7 @@ export class Contact {
throw new Error('unknown short name contact') throw new Error('unknown short name contact')
} }
const { name, address } = accounts[shortName as keyof typeof accounts] const { name, address } = accounts[shortName]
if (typeof name !== 'string') { if (typeof name !== 'string') {
throw new SyntaxError( throw new SyntaxError(

View file

@ -1,4 +1,4 @@
import type SendmailTransport from 'npm:@types/nodemailer' import type SendmailTransport from 'nodemailer'
import type { Mail } from '../types.ts' import type { Mail } from '../types.ts'
import { renderTemplate } from './template.tsx' import { renderTemplate } from './template.tsx'
import { transporter } from './transporter.ts' import { transporter } from './transporter.ts'

View file

@ -1,6 +1,6 @@
import React from 'preact/compat' //for jsx-email import React from 'preact/compat' //for jsx-email
import { render } from 'jsx-email' import { render } from 'jsx-email'
// @deno-types="npm:turndown" // @deno-types="npm:turndown@^7.1.3"
import Turndown from 'turndown' import Turndown from 'turndown'
import type { JSX } from 'preact' import type { JSX } from 'preact'

View file

@ -1,6 +1,6 @@
// @deno-types="npm:@types/nodemailer" // @deno-types="npm:@types/nodemailer@^6.4.15"
import nodemailer from 'nodemailer' import nodemailer from 'nodemailer'
import dkim from '../config/dkim.json' with { type: 'json' } import { dkim } from '../config/loader.ts'
export async function transporter() { export async function transporter() {
const dkimPath = dkim.privateKey const dkimPath = dkim.privateKey