Added email templating, and user welcome email
This commit is contained in:
parent
272cc158ea
commit
348e5f0b20
|
@ -13,3 +13,9 @@ URL=http://localhost:3000
|
|||
DEPLOYMENT=hosted
|
||||
ENABLE_UPDATES=true
|
||||
GOOGLE_ANALYTICS_ID=
|
||||
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_SENDER_EMAIL=
|
39
package.json
39
package.json
|
@ -4,10 +4,8 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"build:webpack":
|
||||
"NODE_ENV=production webpack --config webpack.config.prod.js",
|
||||
"build:analyze":
|
||||
"NODE_ENV=production webpack --config webpack.config.prod.js --json | webpack-bundle-size-analyzer",
|
||||
"build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js",
|
||||
"build:analyze": "NODE_ENV=production webpack --config webpack.config.prod.js --json | webpack-bundle-size-analyzer",
|
||||
"build": "npm run clean && npm run build:webpack",
|
||||
"start": "NODE_ENV=production node index.js",
|
||||
"dev": "NODE_ENV=development nodemon --inspect --watch server index.js",
|
||||
|
@ -20,24 +18,39 @@
|
|||
"sequelize:migrate": "sequelize db:migrate",
|
||||
"test": "npm run test:app && npm run test:server",
|
||||
"test:app": "jest",
|
||||
"test:server":
|
||||
"jest --config=server/.jestconfig.json --runInBand --forceExit",
|
||||
"test:server": "jest --config=server/.jestconfig.json --runInBand --forceExit",
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": ["eslint --fix", "git add"]
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"verbose": false,
|
||||
"roots": ["app"],
|
||||
"roots": [
|
||||
"app"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^.*[.](s?css|css)$": "<rootDir>/__mocks__/styleMock.js",
|
||||
"^.*[.](gif|ttf|eot|svg)$": "<rootDir>/__test__/fileMock.js"
|
||||
},
|
||||
"moduleFileExtensions": ["js", "jsx", "json"],
|
||||
"moduleDirectories": ["node_modules"],
|
||||
"modulePaths": ["app"],
|
||||
"setupFiles": ["<rootDir>/setupJest.js", "<rootDir>/__mocks__/window.js"]
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"jsx",
|
||||
"json"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules"
|
||||
],
|
||||
"modulePaths": [
|
||||
"app"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/setupJest.js",
|
||||
"<rootDir>/__mocks__/window.js"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 7.6"
|
||||
|
@ -117,8 +130,10 @@
|
|||
"mobx-react-devtools": "^4.2.11",
|
||||
"moment": "2.13.0",
|
||||
"node-dev": "3.1.0",
|
||||
"nodemailer": "^4.4.0",
|
||||
"normalize.css": "^7.0.0",
|
||||
"normalizr": "2.0.1",
|
||||
"oy-vey": "^0.10.0",
|
||||
"pg": "^6.1.5",
|
||||
"pg-hstore": "2.3.2",
|
||||
"polished": "1.2.1",
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Mailer #welcome 1`] = `
|
||||
Object {
|
||||
"from": "Outline <hello@mail.getoutline.com>",
|
||||
"html": "
|
||||
<!DOCTYPE html PUBLIC \\"-//W3C//DTD XHTML 1.0 Strict//EN\\" \\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\\">
|
||||
<html
|
||||
|
||||
dir=\\"ltr\\"
|
||||
xmlns=\\"http://www.w3.org/1999/xhtml\\"
|
||||
xmlns:v=\\"urn:schemas-microsoft-com:vml\\"
|
||||
xmlns:o=\\"urn:schemas-microsoft-com:office:office\\">
|
||||
<head>
|
||||
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=utf-8\\" />
|
||||
<meta http-equiv=\\"X-UA-Compatible\\" content=\\"IE=edge\\" />
|
||||
<meta name=\\"viewport\\" content=\\"width=device-width\\"/>
|
||||
|
||||
<title>Welcome to Outline</title>
|
||||
|
||||
<style type=\\"text/css\\">
|
||||
#__bodyTable__{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;font-size:16px;line-height:1.5}
|
||||
|
||||
#__bodyTable__ {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body bgcolor=\\"#FFFFFF\\" width=\\"100%\\" style=\\"-webkit-font-smoothing: antialiased; width:100% !important; background:#FFFFFF;-webkit-text-size-adjust:none; margin:0; padding:0; min-width:100%; direction: ltr;\\">
|
||||
<table bgcolor=\\"#FFFFFF\\" id=\\"__bodyTable__\\" width=\\"100%\\" style=\\"-webkit-font-smoothing: antialiased; width:100% !important; background:#FFFFFF;-webkit-text-size-adjust:none; margin:0; padding:0; min-width:100%\\">
|
||||
<tr>
|
||||
<td align=\\"center\\">
|
||||
<span style=\\"display: none !important; color: #FFFFFF; margin:0; padding:0; font-size:1px; line-height:1px;\\">Outline is a place for your team to build and share knowledge.</span>
|
||||
<table width=\\"550\\" padding=\\"40\\" border=\\"0\\" cellSpacing=\\"0\\" cellPadding=\\"0\\"><tbody><tr><td align=\\"left\\"><table width=\\"100%\\" border=\\"0\\" cellSpacing=\\"0\\" cellPadding=\\"0\\"><tbody><tr><td><table width=\\"100%\\" border=\\"0\\" cellSpacing=\\"0\\" cellPadding=\\"0\\"><tbody><tr><td width=\\"100%\\" height=\\"40px\\" style=\\"line-height:40px;font-size:1px;mso-line-height-rule:exactly\\"> </td></tr></tbody></table><p><strong>Welcome to Outline!</strong></p><p>Outline is a place for your team to build and share knowledge.</p><p>To get started, head to your dashboard and try creating a collection to help document your workflow, create playbooks or help with team onboarding.</p><p>You can also import existing Markdown document by drag and dropping them to your collections</p><table width=\\"100%\\" border=\\"0\\" cellSpacing=\\"0\\" cellPadding=\\"0\\"><tbody><tr><td width=\\"100%\\" height=\\"10px\\" style=\\"line-height:10px;font-size:1px;mso-line-height-rule:exactly\\"> </td></tr></tbody></table><p><a href=\\"http://localhost:3000/dashboard\\" style=\\"display:inline-block;padding:10px 20px;color:#FFFFFF;background:#000000;border-radius:4px;font-weight:500;text-decoration:none;cursor:pointer\\">View my dashboard</a></p><table width=\\"100%\\" border=\\"0\\" cellSpacing=\\"0\\" cellPadding=\\"0\\"><tbody><tr><td width=\\"100%\\" height=\\"40px\\" style=\\"line-height:40px;font-size:1px;mso-line-height-rule:exactly\\"> </td></tr></tbody></table></td></tr></tbody></table><table width=\\"100%\\" border=\\"0\\" cellSpacing=\\"0\\" cellPadding=\\"0\\"><tbody><tr><td width=\\"75%\\" style=\\"padding:20px 0;border-top:1px solid #e8e8e8;color:#9BA6B2;font-size:14px\\"><a href=\\"http://localhost:3000\\" style=\\"color:#9BA6B2;text-decoration:none\\">Outline</a></td></tr></tbody></table></td></tr></tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
",
|
||||
"subject": "Welcome to Outline",
|
||||
"text": "
|
||||
Welcome to Outline!
|
||||
|
||||
Outline is a place for your team to build and share knowledge.
|
||||
|
||||
To get started, head to your dashboard and try creating a collection to help document your workflow, create playbooks or help with team onboarding.
|
||||
|
||||
You can also import existing Markdown document by drag and dropping them to your collections
|
||||
|
||||
http://localhost:3000/dashboard
|
||||
",
|
||||
"to": "user@example.com",
|
||||
}
|
||||
`;
|
|
@ -10,6 +10,12 @@ afterAll(server.close);
|
|||
|
||||
describe.skip('#auth.signup', async () => {
|
||||
it('should signup a new user', async () => {
|
||||
const welcomeEmailMock = jest.fn();
|
||||
jest.doMock('../mailer', () => {
|
||||
return {
|
||||
welcome: welcomeEmailMock,
|
||||
};
|
||||
});
|
||||
const res = await server.post('/api/auth.signup', {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
|
@ -23,6 +29,7 @@ describe.skip('#auth.signup', async () => {
|
|||
expect(res.status).toEqual(200);
|
||||
expect(body.ok).toBe(true);
|
||||
expect(body.data.user).toBeTruthy();
|
||||
expect(welcomeEmailMock).toBeCalledWith('new.user@example.com');
|
||||
});
|
||||
|
||||
it('should require params', async () => {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import EmailTemplate from './components/EmailLayout';
|
||||
import Body from './components/Body';
|
||||
import Button from './components/Button';
|
||||
import Footer from './components/Footer';
|
||||
import EmptySpace from './components/EmptySpace';
|
||||
|
||||
export const welcomeEmailText = `
|
||||
Welcome to Outline!
|
||||
|
||||
Outline is a place for your team to build and share knowledge.
|
||||
|
||||
To get started, head to your dashboard and try creating a collection to help document your workflow, create playbooks or help with team onboarding.
|
||||
|
||||
You can also import existing Markdown document by drag and dropping them to your collections
|
||||
|
||||
${process.env.URL}/dashboard
|
||||
`;
|
||||
|
||||
export const WelcomeEmail = () => {
|
||||
return (
|
||||
<EmailTemplate>
|
||||
<Body>
|
||||
<p>
|
||||
<strong>Welcome to Outline!</strong>
|
||||
</p>
|
||||
|
||||
<p>Outline is a place for your team to build and share knowledge.</p>
|
||||
<p>
|
||||
To get started, head to your dashboard and try creating a collection
|
||||
to help document your workflow, create playbooks or help with team
|
||||
onboarding.
|
||||
</p>
|
||||
<p>
|
||||
You can also import existing Markdown document by drag and dropping
|
||||
them to your collections
|
||||
</p>
|
||||
|
||||
<EmptySpace height={10} />
|
||||
|
||||
<p>
|
||||
<Button href={`${process.env.URL}/dashboard`}>
|
||||
View my dashboard
|
||||
</Button>
|
||||
</p>
|
||||
</Body>
|
||||
|
||||
<Footer />
|
||||
</EmailTemplate>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Table, TBody, TR, TD } from 'oy-vey';
|
||||
|
||||
import EmptySpace from './EmptySpace';
|
||||
|
||||
type Props = {
|
||||
children: React$Element<*>,
|
||||
};
|
||||
|
||||
export default ({ children }: Props) => {
|
||||
return (
|
||||
<Table width="100%">
|
||||
<TBody>
|
||||
<TR>
|
||||
<TD>
|
||||
<EmptySpace height={40} />
|
||||
{children}
|
||||
<EmptySpace height={40} />
|
||||
</TD>
|
||||
</TR>
|
||||
</TBody>
|
||||
</Table>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
export default (props: { href: string, children: React.Element<*> }) => {
|
||||
const style = {
|
||||
display: 'inline-block',
|
||||
padding: '10px 20px',
|
||||
color: '#FFFFFF',
|
||||
background: '#000000',
|
||||
borderRadius: '4px',
|
||||
fontWeight: 500,
|
||||
textDecoration: 'none',
|
||||
cursor: 'pointer',
|
||||
};
|
||||
|
||||
return <a {...props} style={style} />;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Table, TBody, TR, TD } from 'oy-vey';
|
||||
import { fonts } from '../../../shared/styles/constants';
|
||||
|
||||
type Props = {
|
||||
children: React$Element<*>,
|
||||
};
|
||||
|
||||
export default (props: Props) => (
|
||||
<Table width="550" padding="40">
|
||||
<TBody>
|
||||
<TR>
|
||||
<TD align="left">{props.children}</TD>
|
||||
</TR>
|
||||
</TBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
export const baseStyles = `
|
||||
#__bodyTable__ {
|
||||
font-family: ${fonts.regular};
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,29 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Table, TBody, TR, TD } from 'oy-vey';
|
||||
|
||||
const EmptySpace = ({ height }: { height?: number }) => {
|
||||
height = height || 16;
|
||||
const style = {
|
||||
lineHeight: `${height}px`,
|
||||
fontSize: '1px',
|
||||
msoLineHeightRule: 'exactly',
|
||||
};
|
||||
|
||||
return (
|
||||
<Table width="100%">
|
||||
<TBody>
|
||||
<TR>
|
||||
<TD
|
||||
width="100%"
|
||||
height={`${height}px`}
|
||||
style={style}
|
||||
dangerouslySetInnerHTML={{ __html: ' ' }}
|
||||
/>
|
||||
</TR>
|
||||
</TBody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptySpace;
|
|
@ -0,0 +1,31 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Table, TBody, TR, TD } from 'oy-vey';
|
||||
|
||||
export default () => {
|
||||
const style = {
|
||||
padding: '20px 0',
|
||||
borderTop: '1px solid #e8e8e8',
|
||||
color: '#9BA6B2',
|
||||
fontSize: '14px',
|
||||
};
|
||||
|
||||
const linkStyle = {
|
||||
color: '#9BA6B2',
|
||||
textDecoration: 'none',
|
||||
};
|
||||
|
||||
return (
|
||||
<Table width="100%">
|
||||
<TBody>
|
||||
<TR>
|
||||
<TD width="75%" style={style}>
|
||||
<a href={process.env.URL} style={linkStyle}>
|
||||
Outline
|
||||
</a>
|
||||
</TD>
|
||||
</TR>
|
||||
</TBody>
|
||||
</Table>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
// @flow
|
||||
import Koa from 'koa';
|
||||
import Router from 'koa-router';
|
||||
import { Mailer } from '../mailer';
|
||||
|
||||
const emailPreviews = new Koa();
|
||||
const router = new Router();
|
||||
|
||||
router.get('/:type/:format', async ctx => {
|
||||
const previewMailer = new Mailer();
|
||||
let mailerOutput;
|
||||
previewMailer.transporter = {
|
||||
sendMail: data => (mailerOutput = data),
|
||||
};
|
||||
|
||||
switch (ctx.params.type) {
|
||||
case 'welcome':
|
||||
previewMailer.welcome('user@example.com');
|
||||
break;
|
||||
default:
|
||||
console.log(1);
|
||||
}
|
||||
|
||||
if (!mailerOutput) return;
|
||||
|
||||
if (ctx.params.format === 'text') {
|
||||
ctx.body = mailerOutput.text;
|
||||
} else {
|
||||
ctx.body = mailerOutput.html;
|
||||
}
|
||||
});
|
||||
|
||||
emailPreviews.use(router.routes());
|
||||
|
||||
export default emailPreviews;
|
|
@ -8,6 +8,7 @@ import bugsnag from 'bugsnag';
|
|||
import updates from './utils/updates';
|
||||
|
||||
import api from './api';
|
||||
import emails from './emails';
|
||||
import routes from './routes';
|
||||
|
||||
const app = new Koa();
|
||||
|
@ -71,6 +72,10 @@ if (process.env.NODE_ENV === 'production' && process.env.BUGSNAG_KEY) {
|
|||
app.on('error', bugsnag.koaHandler);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
app.use(mount('/emails', emails));
|
||||
}
|
||||
|
||||
app.use(mount('/api', api));
|
||||
app.use(mount(routes));
|
||||
|
||||
|
@ -85,7 +90,7 @@ app.use(
|
|||
|
||||
/**
|
||||
* Production updates and anonymous analytics.
|
||||
*
|
||||
*
|
||||
* Set ENABLE_UPDATES=false to disable them for your installation
|
||||
*/
|
||||
if (
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import nodemailer from 'nodemailer';
|
||||
import Oy from 'oy-vey';
|
||||
import invariant from 'invariant';
|
||||
import { baseStyles } from './emails/components/EmailLayout';
|
||||
|
||||
import { WelcomeEmail, welcomeEmailText } from './emails/WelcomeEmail';
|
||||
|
||||
type SendMailType = {
|
||||
to: string,
|
||||
properties?: any,
|
||||
title: string,
|
||||
previewText?: string,
|
||||
text: string,
|
||||
html: React.Element<*>,
|
||||
headCSS?: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Mailer
|
||||
*
|
||||
* Mailer class to contruct and send emails.
|
||||
*
|
||||
* To preview emails, add a new preview to `emails/index.js` and visit following
|
||||
* URLs in development mode:
|
||||
*
|
||||
* HTML: http://localhost:3000/email/:email_type/html
|
||||
* TEXT: http://localhost:3000/email/:email_type/text
|
||||
*/
|
||||
class Mailer {
|
||||
transporter: ?any;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
sendMail = async (data: SendMailType): ?Promise<*> => {
|
||||
if (this.transporter) {
|
||||
const html = Oy.renderTemplate(data.html, {
|
||||
title: data.title,
|
||||
headCSS: [baseStyles, data.headCSS].join(' '),
|
||||
previewText: data.previewText,
|
||||
});
|
||||
|
||||
invariant(this.transporter, 'very sure this.transporter exists');
|
||||
try {
|
||||
await this.transporter.sendMail({
|
||||
from: process.env.SMTP_SENDER_EMAIL,
|
||||
to: data.to,
|
||||
subject: data.title,
|
||||
html: html,
|
||||
text: data.text,
|
||||
});
|
||||
} catch (e) {
|
||||
Bugsnag.notifyException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
welcome = async (to: string) => {
|
||||
this.sendMail({
|
||||
to,
|
||||
title: 'Welcome to Outline',
|
||||
previewText:
|
||||
'Outline is a place for your team to build and share knowledge.',
|
||||
html: <WelcomeEmail />,
|
||||
text: welcomeEmailText,
|
||||
});
|
||||
};
|
||||
|
||||
constructor() {
|
||||
if (process.env.SMTP_HOST) {
|
||||
let smtpConfig = {
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.SMTP_USERNAME,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
};
|
||||
|
||||
this.transporter = nodemailer.createTransport(smtpConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mailer = new Mailer();
|
||||
|
||||
export { Mailer };
|
||||
export default mailer;
|
|
@ -0,0 +1,19 @@
|
|||
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||
import { Mailer } from './mailer';
|
||||
|
||||
describe('Mailer', () => {
|
||||
let fakeMailer;
|
||||
let sendMailOutput;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeMailer = new Mailer();
|
||||
fakeMailer.transporter = {
|
||||
sendMail: output => (sendMailOutput = output),
|
||||
};
|
||||
});
|
||||
|
||||
test('#welcome', () => {
|
||||
fakeMailer.welcome('user@example.com');
|
||||
expect(sendMailOutput).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -4,6 +4,7 @@ import bcrypt from 'bcrypt';
|
|||
import uuid from 'uuid';
|
||||
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
|
||||
import { uploadToS3FromUrl } from '../utils/s3';
|
||||
import mailer from '../mailer';
|
||||
|
||||
import JWT from 'jsonwebtoken';
|
||||
|
||||
|
@ -99,5 +100,6 @@ const hashPassword = function hashPassword(model) {
|
|||
User.beforeCreate(hashPassword);
|
||||
User.beforeUpdate(hashPassword);
|
||||
User.beforeCreate(setRandomJwtSecret);
|
||||
User.afterCreate(user => mailer.welcome(user.email));
|
||||
|
||||
export default User;
|
||||
|
|
|
@ -37,6 +37,7 @@ productionWebpackConfig.plugins.push(
|
|||
productionWebpackConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
URL: JSON.stringify(process.env.URL),
|
||||
NODE_ENV: JSON.stringify('production'),
|
||||
GOOGLE_ANALYTICS_ID: JSON.stringify(process.env.GOOGLE_ANALYTICS_ID),
|
||||
},
|
||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -1517,6 +1517,12 @@ clean-css@3.4.x:
|
|||
commander "2.8.x"
|
||||
source-map "0.4.x"
|
||||
|
||||
clean-css@^4.0.12:
|
||||
version "4.1.9"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301"
|
||||
dependencies:
|
||||
source-map "0.5.x"
|
||||
|
||||
cli-color@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.2.0.tgz#3a5ae74fd76b6267af666e69e2afbbd01def34d1"
|
||||
|
@ -6175,6 +6181,10 @@ node-pre-gyp@0.6.36, node-pre-gyp@^0.6.36:
|
|||
tar "^2.2.1"
|
||||
tar-pack "^3.4.0"
|
||||
|
||||
nodemailer@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.4.0.tgz#571989e524a906fb83b1518f3e4c0d3140db24af"
|
||||
|
||||
nodemon@1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.11.0.tgz#226c562bd2a7b13d3d7518b49ad4828a3623d06c"
|
||||
|
@ -6492,6 +6502,14 @@ osenv@^0.1.0, osenv@^0.1.4:
|
|||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708"
|
||||
|
||||
oy-vey@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/oy-vey/-/oy-vey-0.10.0.tgz#16160f837f0ea3d0340adfc2377ba93d1ed9ce76"
|
||||
dependencies:
|
||||
clean-css "^4.0.12"
|
||||
object-assign "^4.1.1"
|
||||
sanitizer "^0.1.3"
|
||||
|
||||
p-cancelable@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
|
||||
|
@ -7827,6 +7845,10 @@ sane@~1.6.0:
|
|||
walker "~1.0.5"
|
||||
watch "~0.10.0"
|
||||
|
||||
sanitizer@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/sanitizer/-/sanitizer-0.1.3.tgz#d4f0af7475d9a7baf2a9e5a611718baa178a39e1"
|
||||
|
||||
sax@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
|
||||
|
@ -8177,7 +8199,7 @@ source-map@0.5.6:
|
|||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
|
||||
source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
|
||||
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
||||
|
|
Reference in New Issue