diff --git a/.eslintignore b/.eslintignore index 2c86b4ce6..0060b979a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -25,3 +25,5 @@ test/blanket_mocha.js # TypeScript generated files ts/**/*.js **/ts/**/*.js + +playwright.config.js diff --git a/.gitignore b/.gitignore index 87bd18f96..3259cee47 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ ts/protobuf/*.d.ts ts/**/*.js.map test/ts/**/*.js.map test/ts/**/*.js +ts/test/automation/**/*.js +ts/test/automation/**/*.js.map # Swapfiles @@ -46,3 +48,6 @@ yarn-error.log .vscode/ libloki/test/test.js + +playwright.config.js +playwright.config.js.map diff --git a/.prettierignore b/.prettierignore index 9cbb9d55f..26abf3213 100644 --- a/.prettierignore +++ b/.prettierignore @@ -20,6 +20,7 @@ ts/protobuf/*.d.ts ts/protobuf/*.js stylesheets/manifest.css ts/util/lint/exceptions.json +ts/test/automation/notes # Third-party files node_modules/** diff --git a/.yarnclean b/.yarnclean index 9a42251c3..ff7a12e00 100644 --- a/.yarnclean +++ b/.yarnclean @@ -3,6 +3,7 @@ __tests__ test tests powered-test +!@playwright/test/** # asset directories docs diff --git a/package.json b/package.json index 1e42bd38c..589adff49 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "jquery": "3.3.1", "jsbn": "1.1.0", "libsodium-wrappers": "^0.7.8", + "libxmljs": "^0.19.7", "linkify-it": "3.0.2", "lodash": "4.17.11", "long": "^4.0.0", @@ -174,7 +175,7 @@ "@types/webpack": "^5.28.0", "arraybuffer-loader": "1.0.3", "asar": "0.14.0", - "chai": "4.3.4", + "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-bytes": "^0.1.2", "css-loader": "^3.6.0", @@ -268,7 +269,11 @@ "StartupWMClass": "Session" }, "asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries", - "target": ["deb", "rpm", "freebsd"], + "target": [ + "deb", + "rpm", + "freebsd" + ], "icon": "build/icon.icns" }, "asarUnpack": [ diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..e13235e0b --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,15 @@ +const config = { + timeout: 300000, + globalTimeout: 6000000, + reporter: 'list', + testDir: './ts/test/automation', + testIgnore: '*.js', + outputDir: './ts/test/automation/test-results', + use: { + video: 'retain-on-failure', + trace: 'retain-on-failure', + }, + workers: 1, +}; + +module.exports = config; diff --git a/ts/components/conversation/SessionMessagesListContainer.tsx b/ts/components/conversation/SessionMessagesListContainer.tsx index eab0e9ec5..eb72176dc 100644 --- a/ts/components/conversation/SessionMessagesListContainer.tsx +++ b/ts/components/conversation/SessionMessagesListContainer.tsx @@ -166,6 +166,7 @@ class SessionMessagesListContainerInner extends React.Component { className="messages-container" onScroll={this.handleScroll} ref={this.props.messageContainerRef} + data-testid="messages-container" > diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index 3def48e30..920915853 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -384,6 +384,7 @@ class CompositionBoxInner extends React.Component { ref={el => { this.container = el; }} + data-testid="message-input" > {this.renderTextArea()} diff --git a/ts/components/conversation/composition/CompositionButtons.tsx b/ts/components/conversation/composition/CompositionButtons.tsx index b6b047c8c..6f3046daf 100644 --- a/ts/components/conversation/composition/CompositionButtons.tsx +++ b/ts/components/conversation/composition/CompositionButtons.tsx @@ -54,6 +54,7 @@ export const SendMessageButton = (props: { onClick: () => void }) => { borderRadius="300px" iconPadding="6px" onClick={props.onClick} + dataTestId="send-message-button" /> ); diff --git a/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx b/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx index 9c039d123..0e343981c 100644 --- a/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx +++ b/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx @@ -25,6 +25,7 @@ type Props = { onQuoteClick: (quote: QuoteClickOptions) => void; ctxMenuID: string; isDetailView?: boolean; + dataTestId?: string; }; export const MessageContentWithStatuses = (props: Props) => { @@ -64,7 +65,7 @@ export const MessageContentWithStatuses = (props: Props) => { } }; - const { messageId, onQuoteClick, ctxMenuID, isDetailView } = props; + const { messageId, onQuoteClick, ctxMenuID, isDetailView, dataTestId } = props; if (!contentProps) { return null; } @@ -78,8 +79,13 @@ export const MessageContentWithStatuses = (props: Props) => { onClick={onClickOnMessageOuterContainer} onDoubleClickCapture={onDoubleClickReplyToMessage} style={{ width: hasAttachments && isTrustedForAttachmentDownload ? 'min-content' : 'auto' }} + data-testid={dataTestId} > - +
@@ -89,7 +95,11 @@ export const MessageContentWithStatuses = (props: Props) => { onQuoteClick={onQuoteClick} />
- + {!isDeleted && } ); diff --git a/ts/components/conversation/message/message-content/MessageStatus.tsx b/ts/components/conversation/message/message-content/MessageStatus.tsx index 7db8530d5..be2182514 100644 --- a/ts/components/conversation/message/message-content/MessageStatus.tsx +++ b/ts/components/conversation/message/message-content/MessageStatus.tsx @@ -7,12 +7,13 @@ import { OutgoingMessageStatus } from './OutgoingMessageStatus'; type Props = { isCorrectSide: boolean; messageId: string; + dataTestId?: string; }; export type MessageStatusSelectorProps = Pick; export const MessageStatus = (props: Props) => { - const { isCorrectSide } = props; + const { isCorrectSide, dataTestId } = props; const selected = useSelector(state => getMessageStatusProps(state as any, props.messageId)); if (!selected) { @@ -30,5 +31,5 @@ export const MessageStatus = (props: Props) => { return null; } - return ; + return ; }; diff --git a/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx b/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx index e287fa02c..d4ed18194 100644 --- a/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx +++ b/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx @@ -12,57 +12,66 @@ const MessageStatusSendingContainer = styled.div` cursor: pointer; `; -const MessageStatusSending = () => { +const MessageStatusSending = ({ dataTestId }: { dataTestId?: string }) => { const iconColor = 'var(--color-text)'; return ( - + ); }; -const MessageStatusSent = () => { +const MessageStatusSent = ({ dataTestId }: { dataTestId?: string }) => { const iconColor = 'var(--color-text)'; return ( - + ); }; -const MessageStatusRead = () => { +const MessageStatusRead = ({ dataTestId }: { dataTestId?: string }) => { const iconColor = 'var(--color-text)'; return ( - + ); }; -const MessageStatusError = () => { +const MessageStatusError = ({ dataTestId }: { dataTestId?: string }) => { const showDebugLog = () => { ipcRenderer.send('show-debug-log'); }; return ( - + ); }; -export const OutgoingMessageStatus = (props: { status?: MessageDeliveryStatus | null }) => { - switch (props.status) { +export const OutgoingMessageStatus = (props: { + status?: MessageDeliveryStatus | null; + dataTestId?: string; +}) => { + const { status, dataTestId } = props; + switch (status) { case 'sending': - return ; + return ; case 'sent': - return ; + return ; case 'read': - return ; + return ; case 'error': - return ; + return ; default: return null; } diff --git a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx index 280edf82f..a9939403a 100644 --- a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx @@ -181,6 +181,7 @@ export const GenericReadableMessage = (props: Props) => { messageId={messageId} onQuoteClick={props.onQuoteClick} isDetailView={isDetailView} + dataTestId={`message-content-${messageId}`} /> {
{label} {isMessageSection && ( - + )} diff --git a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx index d636866e0..3685bf764 100644 --- a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx +++ b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx @@ -114,6 +114,7 @@ export const OverlayClosedGroup = () => { text={buttonText} disabled={noContactsForClosedGroup} onClick={onEnterPressed} + dataTestId="next-button" />
); diff --git a/ts/components/registration/SignInTab.tsx b/ts/components/registration/SignInTab.tsx index 694961de1..000ffe2d2 100644 --- a/ts/components/registration/SignInTab.tsx +++ b/ts/components/registration/SignInTab.tsx @@ -55,6 +55,7 @@ const ContinueYourSessionButton = (props: { buttonColor={SessionButtonColor.Green} text={window.i18n('continueYourSession')} disabled={props.disabled} + dataTestId="continue-session-button;" /> ); }; diff --git a/ts/test/automation/check_password.spec.ts b/ts/test/automation/check_password.spec.ts new file mode 100644 index 000000000..b60035fd2 --- /dev/null +++ b/ts/test/automation/check_password.spec.ts @@ -0,0 +1,27 @@ +import { _electron, Page, test } from '@playwright/test'; +import { newUser } from './new_user'; +import { openApp } from './open'; +// Open app +let window: Page | undefined; +test('Check Password', async () => { + // open Electron + window = await openApp('1'); + // Create user + await newUser(window, 'userA'); + // Click on settings tab + await window.click('[data-testid=settings-section]'); + // Click on privacy + await window.click('"Privacy"'); + // Click set password + await window.click('"Set Password"'); + // Enter password + await window.type('#password-modal-input', '123456'); + // Confirm password + await window.type('#password-modal-input-confirm', '123456'); + // Click OK + await window.keyboard.press('Enter'); + // Type password into input field + await window.fill('#password-lock-input', '123456'); + // Click OK + await window.click('"OK"'); +}); diff --git a/ts/test/automation/clean_up.ts b/ts/test/automation/clean_up.ts new file mode 100644 index 000000000..8295902a7 --- /dev/null +++ b/ts/test/automation/clean_up.ts @@ -0,0 +1,12 @@ +import test, { _electron, Page } from '@playwright/test'; +import { getAppDataPath } from './open'; + + + +export const cleanUp = async (window: Page) => { + await window.click('[data-testid=settings-section]'); + await window.click('text=Clear All Data'); + await window.click('text=Entire Account'); + await window.click('text=I am sure'); + await window.waitForTimeout(10000); +}; diff --git a/ts/test/automation/create_group.spec.ts b/ts/test/automation/create_group.spec.ts new file mode 100644 index 000000000..022af0b28 --- /dev/null +++ b/ts/test/automation/create_group.spec.ts @@ -0,0 +1,48 @@ +import { _electron, test } from '@playwright/test'; +import { newUser } from './new_user'; +import { openApp } from './open'; +import { sendMessage } from './send_message'; + +const userADisplayName = 'userA'; +const userBDisplayName = 'userB'; +const userCDisplayName = 'userC'; + +const testMessage = 'Sending Test Message'; +const testReply = 'Sending Reply Test Message'; + +test('Create group', async () => { + // Open Electron + const [windowA, windowB, windowC] = await Promise.all([openApp('1'), openApp('2'), openApp('3')]); + // Create User x3 + // create userA + const userA = await newUser(windowA, userADisplayName); + // create userB + const userB = await newUser(windowB, userBDisplayName); + // Create UserC + const userC = await newUser(windowC, userCDisplayName); + // Add contact + await sendMessage(windowA, userB.sessionid, testMessage); + await sendMessage(windowB, userA.sessionid, testReply); + await sendMessage(windowA, userC.sessionid, testMessage); + await sendMessage(windowC, userA.sessionid, testReply); + // Create group with existing contact and session ID (of non-contact) + // Click new closed group tab + await windowA.click('"New Closed Group"'); + // Enter group name + await windowA.fill('.session-id-editable', 'Test Group'); + // Select user B + await windowA.click(userBDisplayName); + // Select user C + await windowA.click(userCDisplayName); + // Click Done + await windowA.click('"Done"'); + // Check group was successfully created + windowA.locator(`text=${userBDisplayName}, ${userCDisplayName} + 'You joined the group'`); + // Send message in group chat from user a + await windowA.fill('[data-testid=message-input] * textarea', testMessage); + // Verify it was received by other two accounts + // Send message from user 2 + // Verify + // Send message from user 3 + // Verify +}); diff --git a/ts/test/automation/electron_test.spec.ts b/ts/test/automation/electron_test.spec.ts new file mode 100644 index 000000000..138492f84 --- /dev/null +++ b/ts/test/automation/electron_test.spec.ts @@ -0,0 +1,33 @@ +import { _electron, expect, Page, test } from '@playwright/test'; +import { newUser } from './new_user'; +import { openApp } from './open'; +import { sleepFor } from '../../session/utils/Promise'; + +// import {emptyDirSync} from 'fs-extra'; + +let window: Page | undefined; + +test('Create User', async () => { + // Launch Electron app. + window = await openApp('1'); + // Create User + const userA = await newUser(window, 'userA'); + + await window.click('[data-testid=leftpane-primary-avatar]'); + await sleepFor(100); + //check username matches + expect(await window.innerText('[data-testid=your-profile-name]')).toBe(userA.userName); + //check session id matches + expect(await window.innerText('[data-testid=your-session-id]')).toBe(userA.sessionid); + // exit profile module + await window.click('.session-icon-button.small'); + // go to settings section + await window.click('[data-testid=settings-section]'); + await window.click('text=Recovery Phrase'); + // check recovery phrase matches + expect(await window.innerText('[data-testid=recovery-phrase-seed-modal]')).toBe( + userA.recoveryPhrase + ); + // Exit profile module + await window.click('.session-icon-button.small'); +}); diff --git a/ts/test/automation/log_in.ts b/ts/test/automation/log_in.ts new file mode 100644 index 000000000..451097857 --- /dev/null +++ b/ts/test/automation/log_in.ts @@ -0,0 +1,15 @@ +import { _electron, Page } from '@playwright/test'; +import { sleepFor } from '../../session/utils/Promise'; + +export const logIn = async (window: Page, userName: string, recoveryPhrase: string) => { + // restore account + await window.click('[data-testid=restore-using-recovery'); + // Enter recovery phrase + await window.fill('[data-testid=recovery-phrase-input]', recoveryPhrase); + // Enter display name + await window.fill('[data-testid=display-name-input]', userName); + // Click continue your session + await window.click('[data-testid=continue-session-button]'); + + await sleepFor(100); +}; diff --git a/ts/test/automation/new_contact_test.spec.ts b/ts/test/automation/new_contact_test.spec.ts new file mode 100644 index 000000000..e22f593c9 --- /dev/null +++ b/ts/test/automation/new_contact_test.spec.ts @@ -0,0 +1,36 @@ +import { _electron, expect, test } from '@playwright/test'; +import { newUser } from './new_user'; +import { openApp } from './open'; +import { sendMessage } from './send_message'; + +const userADisplayName = 'userA'; +const userBDisplayName = 'userB'; + +const timeStamp = Date.now(); + +const testMessage = 'Test-Message-'; +const testReply = 'Sending Reply Test Message'; + +// Send message in one to one conversation with new contact +test('Send message to new contact', async () => { + const [windowA, windowB] = await Promise.all([openApp('1'), openApp('2')]); + // Create User A + const userA = await newUser(windowA, userADisplayName); + // Create User B + const userB = await newUser(windowB, userBDisplayName); + // User A sends message to User B + await sendMessage(windowA, userB.sessionid, `${testMessage} + ${timeStamp}`); + windowA.locator(`${testMessage} > svg`).waitFor; + await windowA.isVisible('[data-testid=msg-status-outgoing]'); + await windowA.waitForTimeout(5500); + // User B sends message to User B to USER A + await sendMessage(windowB, userA.sessionid, `${testReply} + ${timeStamp}`); + await windowA.waitForTimeout(5500); + // Navigate to contacts tab in User B's window + await windowB.click('[data-testid=contact-section]'); + await windowA.waitForTimeout(2500); + expect(await windowB.innerText('.module-conversation__user__profile-name')).toBe(userA.userName); + // Navigate to contacts tab in User A's window + await windowA.click('[data-testid=contact-section]'); + expect(await windowA.innerText('.module-conversation__user__profile-name')).toBe(userB.userName); +}); diff --git a/ts/test/automation/new_user.ts b/ts/test/automation/new_user.ts new file mode 100644 index 000000000..9e301cab1 --- /dev/null +++ b/ts/test/automation/new_user.ts @@ -0,0 +1,20 @@ +import { _electron, Page } from '@playwright/test'; + +export const newUser = async (window: Page, userName: string) => { + // Create User + await window.click('text=Create Session ID'); + // Wait for animation for finish creating ID + await window.waitForTimeout(1500); + //Save session ID to a variable + const sessionid = await window.inputValue('[data-testid=session-id-signup]'); + await window.click('text=Continue'); + // Input username = testuser + await window.fill('#session-input-floating-label', userName); + await window.click('text=Get Started'); + // save recovery phrase + await window.click('text=Reveal recovery phrase'); + const recoveryPhrase = await window.innerText('[data-testid=recovery-phrase-seed-modal]'); + + await window.click('.session-icon-button.small'); + return { userName, sessionid, recoveryPhrase }; +}; diff --git a/ts/test/automation/open.ts b/ts/test/automation/open.ts new file mode 100644 index 000000000..c50e79c2d --- /dev/null +++ b/ts/test/automation/open.ts @@ -0,0 +1,63 @@ +import test, { _electron } from '@playwright/test'; +import { readdirSync, rmdirSync } from 'fs'; + +import * as path from 'path'; + +const NODE_ENV = 'test-integration'; + +let appDataPath: undefined | string; + +test.beforeAll(async () => { + appDataPath = await getAppDataPath(); +}); + +const getDirectoriesOfSessionDataPath = (source: string) => + readdirSync(source, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) + .filter(n => n.startsWith(`Session-${NODE_ENV}`)); + +test.beforeEach(() => { + if (!appDataPath || !appDataPath.length) { + throw new Error('appDataPath unset'); + } + const parentFolderOfAllDataPath = path.dirname(appDataPath); + + if (!parentFolderOfAllDataPath || parentFolderOfAllDataPath.length < 20) { + throw new Error('parentFolderOfAllDataPath not found or invalid'); + } + + const allAppDataPath = getDirectoriesOfSessionDataPath(parentFolderOfAllDataPath); + + allAppDataPath.map(folder => { + if (!appDataPath) { + throw new Error('parentFolderOfAllDataPath unset'); + } + const pathToRemove = path.join(parentFolderOfAllDataPath, folder); + console.warn('Removing old test data left at: ', pathToRemove); + rmdirSync(pathToRemove, { recursive: true }); + }); +}); + +export const getAppDataPath = async () => { + process.env.NODE_ENV = NODE_ENV; + const electronApp = await _electron.launch({ args: ['main.js'] }); + const appPath = await electronApp.evaluate(async ({ app }) => { + return app.getPath('userData'); + }); + const window = await electronApp.firstWindow(); + await window.close(); + + return appPath; +}; + +export const openApp = async (multi: string) => { + process.env.NODE_APP_INSTANCE = multi; + process.env.NODE_ENV = NODE_ENV; + const electronApp = await _electron.launch({ args: ['main.js'] }); + // Get the first window that the app opens, wait if necessary. + const window = await electronApp.firstWindow(); + + await window.reload(); + return window; +}; diff --git a/ts/test/automation/send_message.ts b/ts/test/automation/send_message.ts new file mode 100644 index 000000000..379f5824f --- /dev/null +++ b/ts/test/automation/send_message.ts @@ -0,0 +1,13 @@ +import { _electron, Page } from '@playwright/test'; + +export const sendMessage = async (window: Page, sessionid: string, message: string) => { + await window.click('[data-testid=new-conversation-button]'); + // Enter session ID of USER B + await window.fill('.session-id-editable-textarea', sessionid); + // click next + await window.click('text=Next'); + // type into message input box + await window.fill('[data-testid=message-input] * textarea', message); + // click up arrow (send) + await window.click('[data-testid=send-message-button]'); +}; diff --git a/yarn.lock b/yarn.lock index 381f87435..ef06b7a24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2210,6 +2210,11 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bindings@~1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" + integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== + biskviit@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/biskviit/-/biskviit-1.0.1.tgz#037a0cd4b71b9e331fd90a1122de17dc49e420a7" @@ -2590,7 +2595,7 @@ chai-bytes@^0.1.2: resolved "https://registry.yarnpkg.com/chai-bytes/-/chai-bytes-0.1.2.tgz#c297e81d47eb3106af0676ded5bb5e0c9f981db3" integrity sha512-0ol6oJS0y1ozj6AZK8n1pyv1/G+l44nqUJygAkK1UrYl+IOGie5vcrEdrAlwmLYGIA9NVvtHWosPYwWWIXf/XA== -chai@4.3.4: +chai@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== @@ -2674,7 +2679,7 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: +chownr@^1.1.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -3454,6 +3459,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-node@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" @@ -4565,7 +4575,7 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^1.2.5: +fs-minipass@^1.2.5, fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== @@ -5295,7 +5305,7 @@ iconv-lite@0.4.13: resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" integrity sha1-H4irpKsLFQjoMSrMOTRfNumS4vI= -iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -5316,6 +5326,13 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" +ignore-walk@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" + integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== + dependencies: + minimatch "^3.0.4" + ignore@^3.3.3: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" @@ -6166,6 +6183,15 @@ libsodium@0.7.8: resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.8.tgz#fbd12247b7b1353f88d8de1cbc66bc1a07b2e008" integrity sha512-/Qc+APf0jbeWSaeEruH0L1/tbbT+sbf884ZL0/zV/0JXaDPBzYkKbyb/wmxMHgAHzm3t6gqe7bOOXAVwfqVikQ== +libxmljs@^0.19.7: + version "0.19.7" + resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.7.tgz#96c2151b0b73f33dd29917edec82902587004e5a" + integrity sha512-lFJyG9T1mVwTzNTw6ZkvIt0O+NsIR+FTE+RcC2QDFGU8YMnQrnyEOGrj6HWSe1AdwQK7s37BOp4NL+pcAqfK2g== + dependencies: + bindings "~1.3.0" + nan "~2.14.0" + node-pre-gyp "~0.11.0" + lie@*: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -6701,7 +6727,7 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minizlib@^1.1.1: +minizlib@^1.1.1, minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== @@ -6863,7 +6889,7 @@ mz@^2.3.1: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@2.14.2, nan@^2.13.2: +nan@2.14.2, nan@^2.13.2, nan@~2.14.0: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== @@ -6921,6 +6947,15 @@ ncp@~2.0.0: resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= +needle@^2.2.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" + integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -6996,6 +7031,22 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= +node-pre-gyp@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + node-releases@^1.1.71: version "1.1.73" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" @@ -7041,6 +7092,14 @@ node-sass@6.0.1: dependencies: abbrev "1" +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -7092,6 +7151,13 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +npm-bundled@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== + dependencies: + npm-normalize-package-bin "^1.0.1" + npm-conf@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" @@ -7100,6 +7166,20 @@ npm-conf@^1.1.3: config-chain "^1.1.11" pify "^3.0.0" +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -7114,7 +7194,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.1.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -7282,7 +7362,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@0: +osenv@0, osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -8027,7 +8107,7 @@ rc-util@^4.0.4, rc-util@^4.15.3, rc-util@^4.4.0: react-lifecycles-compat "^3.0.4" shallowequal "^1.1.0" -rc@^1.2.8: +rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -8618,7 +8698,7 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -rimraf@2, rimraf@^2.2.8, rimraf@^2.6.3: +rimraf@2, rimraf@^2.2.8, rimraf@^2.6.1, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -8718,6 +8798,11 @@ safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-json-stringify@~1: version "1.2.0" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" @@ -9597,6 +9682,19 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" +tar@^4: + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" + tar@^6.0.2, tar@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" @@ -10496,7 +10594,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==