diff --git a/packages/mail/dist-in/_cli.d.ts b/packages/mail/dist-in/_cli.d.ts new file mode 100644 index 00000000..9a2cd471 --- /dev/null +++ b/packages/mail/dist-in/_cli.d.ts @@ -0,0 +1,3 @@ +import { IOptions } from './types.js'; +export declare const defaults: () => void; +export declare const sanitize: (argv: any) => IOptions | boolean; diff --git a/packages/mail/dist-in/_cli.js b/packages/mail/dist-in/_cli.js new file mode 100644 index 00000000..fa4b4242 --- /dev/null +++ b/packages/mail/dist-in/_cli.js @@ -0,0 +1,67 @@ +import { forward_slash, pathInfo, substitute, globBase } from "@polymech/commons"; +import { isFile, resolve } from "@polymech/commons"; +import { sync as exists } from "@polymech/fs/exists"; +export const defaults = () => { + const DefaultCommand = 'info'; + if (process.argv.length === 2) { + process.argv.push(DefaultCommand); + } + process.on('unhandledRejection', (reason) => { + console.error('Unhandled rejection, reason: ', reason); + }); +}; +export const sanitize = (argv) => { + const options = { + src: argv.src, + dry: argv.dry, + alt: argv.alt, + logLevel: argv.logLevel, + transport: argv.transport, + ...argv + }; + let srcInfo; + let variables = { + ...options.variables + }; + if (options.src) { + const srcIn = resolve(options.src, options.alt, variables); + options.src = forward_slash(substitute(options.alt, srcIn, variables)); + // in case a file with a glob pattern is provided, strip the glob + // this is a special case, enabling shared scripts in Alt-Tap Salamand + const glob_base = globBase(options.src); + const file = options.src.replace(glob_base.glob, '').replace(/\/$/, ''); + if (exists(file) && isFile(file)) { + options.src = file; + } + srcInfo = pathInfo(resolve(options.src, options.alt, variables)); + if (srcInfo && srcInfo.FILES && srcInfo.FILES.length) { + options.srcInfo = srcInfo; + for (const key in srcInfo) { + if (Object.prototype.hasOwnProperty.call(srcInfo, key)) { + variables['SRC_' + key] = srcInfo[key]; + } + } + } + else { + options.src = resolve(options.src, options.alt, variables); + } + } + const out = resolve(options.dst || "", options.alt, variables); + options.dstInfo = pathInfo(out); + if (options.dst) { + if (options.srcInfo && options.dstInfo) { + options.dstInfo.PATH = options.dst; + for (const key in options.dstInfo) { + if (Object.prototype.hasOwnProperty.call(options.dstInfo, key)) { + variables['DST_' + key] = options.dstInfo[key]; + } + } + } + else { + options.dst = resolve(options.dst || '', options.alt, variables); + } + } + options.variables = variables; + return options; +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2NsaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9fY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxhQUFhLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNqRixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ25ELE9BQU8sRUFBRSxJQUFJLElBQUksTUFBTSxFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFFcEQsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHLEdBQUcsRUFBRTtJQUN6QixNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUM7SUFDOUIsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUM1QixPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztJQUN0QyxDQUFDO0lBQ0QsT0FBTyxDQUFDLEVBQUUsQ0FBQyxvQkFBb0IsRUFBRSxDQUFDLE1BQWMsRUFBRSxFQUFFO1FBQ2hELE9BQU8sQ0FBQyxLQUFLLENBQUMsK0JBQStCLEVBQUUsTUFBTSxDQUFDLENBQUE7SUFDMUQsQ0FBQyxDQUFDLENBQUE7QUFDTixDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUFTLEVBQXNCLEVBQUU7SUFFdEQsTUFBTSxPQUFPLEdBQWE7UUFDdEIsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO1FBQ3ZCLFNBQVMsRUFBRSxJQUFJLENBQUMsU0FBUztRQUN6QixHQUFHLElBQUk7S0FDRSxDQUFBO0lBRWIsSUFBSSxPQUFPLENBQUE7SUFFWCxJQUFJLFNBQVMsR0FB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 \ No newline at end of file diff --git a/packages/mail/src/__tests__/mail.test.ts b/packages/mail/src/__tests__/mail.test.ts new file mode 100644 index 00000000..22800ada --- /dev/null +++ b/packages/mail/src/__tests__/mail.test.ts @@ -0,0 +1,74 @@ + +import { describe, it, expect, beforeAll, vi } from 'vitest' +import nodemailer from 'nodemailer' +import { test as sendTestEmail } from '../lib/nodemailer/index.js' +import { IOptions, ITransportOptions } from '../types.js' + +// Mock dependencies +vi.mock('@polymech/commons', async () => { + return { + CONFIG_DEFAULT: vi.fn(), + forward_slash: (s: string) => s, + pathInfo: () => ({}), + substitute: (_: any, s: string) => s, + resolve: (_: any, s: string) => s, + globBase: () => ({ glob: '', base: '' }), + isFile: () => true, + exists: () => true + } +}) + +// We need to import the mocked module to control the mock +import { CONFIG_DEFAULT } from '@polymech/commons' + +describe('Mail E2E Tests', () => { + let testAccount: nodemailer.TestAccount + + beforeAll(async () => { + // Create a real SMTP account on Ethereal for testing + testAccount = await nodemailer.createTestAccount() + }) + + it('should send a test email using ethereal account', async () => { + const transportConfig: ITransportOptions = { + host: testAccount.smtp.host, + port: testAccount.smtp.port, + secure: testAccount.smtp.secure, + auth: { + user: testAccount.user, + pass: testAccount.pass + } + } + + // Setup the mock to return our test transport configuration + vi.mocked(CONFIG_DEFAULT).mockReturnValue({ + email: { + 'ethereal': transportConfig + } + }) + + const options: IOptions = { + transport: 'ethereal', + from: 'sender@example.com', + to: 'recipient@example.com', + subject: 'Vitest E2E Test', + html: '

Hello from Vitest!

', + logLevel: 'error' // suppress logs during test + } + + const info = await sendTestEmail(options) + + if (!info) { + throw new Error('Email sending failed, info is undefined') + } + + expect(info).toBeDefined() + expect(info.messageId).toBeDefined() + expect(info.accepted).toContain('recipient@example.com') + + // Ethereal allows us to preview the email + const previewUrl = nodemailer.getTestMessageUrl(info) + console.log('Preview URL: %s', previewUrl) + expect(previewUrl).toBeTruthy() + }) +}) diff --git a/packages/mail/vitest.config.ts b/packages/mail/vitest.config.ts new file mode 100644 index 00000000..88f7dcd4 --- /dev/null +++ b/packages/mail/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + environment: 'node', + }, +})