diff --git a/.gitignore b/.gitignore index 7bf2c49778065..3eda69d0c83a4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ site/storybook-static/ site/test-results/ site/yarn-error.log coverage/ +site/**/*.typegen.ts # Build dist/ diff --git a/Makefile b/Makefile index 5f7d5fb9b21d1..c0e7551ad60c3 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,7 @@ provisionersdk/proto: provisionersdk/proto/provisioner.proto site/out: ./scripts/yarn_install.sh + cd site && yarn typegen cd site && yarn build # Restores GITKEEP files! git checkout HEAD site/out diff --git a/README.md b/README.md index c7e802701fbd1..c07cb1d3d2fce 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ To manually run the server and go through first-time set up, run the following c You'll now be able to login and access the server. -To create a project, run: - `dist/coder_linux_amd64/coder projects create -d /path/to/project` ### Development @@ -63,6 +62,10 @@ The `develop.sh` script does three things: This is the recommend flow for working on the front-end, as hot-reload is set up as part of the webpack config. +Note that `./develop.sh` creates a user and allows you to log into the UI, but does not log you into the CLI, which is required for creating a project. Use the `login` command above before the `projects create` command. + +While we're working on automating XState typegen, you may need to run `yarn typegen` from `site`. + ## Front-End Plan For the front-end team, we're planning on 2 phases to the 'v2' work: diff --git a/site/.eslintignore b/site/.eslintignore index 0f3e9e0e60fcb..d582fd9cc67ed 100644 --- a/site/.eslintignore +++ b/site/.eslintignore @@ -8,3 +8,4 @@ coverage .next storybook-static test-results +**/*.typegen.ts diff --git a/site/jest.setup.ts b/site/jest.setup.ts index 71ac0a695c1da..90c669b04d6ab 100644 --- a/site/jest.setup.ts +++ b/site/jest.setup.ts @@ -1,3 +1,19 @@ +import { server } from "./src/test_helpers/server" + +// Establish API mocking before all tests through MSW. +beforeAll(() => + server.listen({ + onUnhandledRequest: "warn", + }), +) + +// Reset any request handlers that we may add during the tests, +// so they don't affect other tests. +afterEach(() => server.resetHandlers()) + +// Clean up after the tests are finished. +afterAll(() => server.close()) + // Helper utility to fail jest tests if a console.error is logged // Pulled from this blog post: // https://www.benmvp.com/blog/catch-warnings-jest-tests/ diff --git a/site/package.json b/site/package.json index 664c5ded39b25..f20d3ccabb8f6 100644 --- a/site/package.json +++ b/site/package.json @@ -4,6 +4,7 @@ "repository": "https://github.com/coder/coder", "private": true, "scripts": { + "postinstall": "yarn typegen", "build": "NODE_ENV=production webpack build --config=webpack.prod.ts", "build:analyze": "NODE_ENV=production webpack --profile --progress --json --config=webpack.prod.ts > out/stats.json && webpack-bundle-analyzer out/stats.json out", "dev": "webpack-dev-server --config=webpack.dev.ts", @@ -18,12 +19,14 @@ "storybook:build": "build-storybook", "test": "jest --selectProjects test", "test:coverage": "jest --selectProjects test --collectCoverage", - "test:watch": "jest --selectProjects test --watch" + "test:watch": "jest --selectProjects test --watch", + "typegen": "xstate typegen 'src/**/*.ts'" }, "dependencies": { "@material-ui/core": "4.9.4", "@material-ui/icons": "4.5.1", "@material-ui/lab": "4.0.0-alpha.42", + "@xstate/react": "^2.0.1", "axios": "0.26.1", "formik": "2.2.9", "history": "5.3.0", @@ -31,6 +34,7 @@ "react-dom": "17.0.2", "react-router-dom": "6.2.2", "swr": "1.2.2", + "xstate": "^4.30.6", "yup": "0.32.11" }, "devDependencies": { @@ -43,13 +47,14 @@ "@storybook/react": "6.4.19", "@testing-library/react": "12.1.4", "@types/express": "4.17.13", - "@types/jest": "27.4.1", + "@types/jest": "^27.4.1", "@types/node": "14.18.12", "@types/react": "17.0.40", "@types/react-dom": "17.0.13", "@types/superagent": "4.1.15", "@typescript-eslint/eslint-plugin": "5.15.0", "@typescript-eslint/parser": "5.15.0", + "@xstate/cli": "^0.1.4", "copy-webpack-plugin": "10.2.4", "eslint": "8.11.0", "eslint-config-prettier": "8.5.0", @@ -66,6 +71,7 @@ "jest": "27.5.1", "jest-junit": "13.0.0", "jest-runner-eslint": "1.0.0", + "msw": "^0.39.2", "prettier": "2.6.0", "react-hot-loader": "4.13.0", "sql-formatter": "4.0.2", diff --git a/site/src/api.test.ts b/site/src/api/index.test.ts similarity index 97% rename from site/src/api.test.ts rename to site/src/api/index.test.ts index 68da3fa56de5d..ec707499316f0 100644 --- a/site/src/api.test.ts +++ b/site/src/api/index.test.ts @@ -1,5 +1,6 @@ import axios from "axios" -import { APIKeyResponse, getApiKey, login, LoginResponse, logout } from "./api" +import { getApiKey, login, logout } from "." +import { LoginResponse, APIKeyResponse } from "./types" // Mock the axios module so that no real network requests are made, but rather // we swap in a resolved or rejected value diff --git a/site/src/api.ts b/site/src/api/index.ts similarity index 53% rename from site/src/api.ts rename to site/src/api/index.ts index 3cdfd59613db3..30b63da9cec81 100644 --- a/site/src/api.ts +++ b/site/src/api/index.ts @@ -1,26 +1,12 @@ import axios, { AxiosRequestHeaders } from "axios" import { mutate } from "swr" +import * as Types from "./types" const CONTENT_TYPE_JSON: AxiosRequestHeaders = { "Content-Type": "application/json", } -/** - * `Organization` must be kept in sync with the go struct in organizations.go - */ -export interface Organization { - id: string - name: string - created_at: string - updated_at: string -} - -export interface Provisioner { - id: string - name: string -} - -export const provisioners: Provisioner[] = [ +export const provisioners: Types.Provisioner[] = [ { id: "terraform", name: "Terraform", @@ -31,25 +17,8 @@ export const provisioners: Provisioner[] = [ }, ] -// This must be kept in sync with the `Project` struct in the back-end -export interface Project { - id: string - created_at: string - updated_at: string - organization_id: string - name: string - provisioner: string - active_version_id: string -} - -export interface CreateProjectRequest { - name: string - organizationId: string - provisioner: string -} - export namespace Project { - export const create = async (request: CreateProjectRequest): Promise => { + export const create = async (request: Types.CreateProjectRequest): Promise => { const response = await fetch(`/api/v2/projects/${request.organizationId}/`, { method: "POST", headers: { @@ -68,23 +37,8 @@ export namespace Project { } } -export interface CreateWorkspaceRequest { - name: string - project_id: string -} - -// Must be kept in sync with backend Workspace struct -export interface Workspace { - id: string - created_at: string - updated_at: string - owner_id: string - project_id: string - name: string -} - export namespace Workspace { - export const create = async (request: CreateWorkspaceRequest): Promise => { + export const create = async (request: Types.CreateWorkspaceRequest): Promise => { const response = await fetch(`/api/v2/users/me/workspaces`, { method: "POST", headers: { @@ -108,17 +62,13 @@ export namespace Workspace { } } -export interface LoginResponse { - session_token: string -} - -export const login = async (email: string, password: string): Promise => { +export const login = async (email: string, password: string): Promise => { const payload = JSON.stringify({ email, password, }) - const response = await axios.post("/api/v2/users/login", payload, { + const response = await axios.post("/api/v2/users/login", payload, { headers: { ...CONTENT_TYPE_JSON }, }) @@ -129,11 +79,12 @@ export const logout = async (): Promise => { await axios.post("/api/v2/users/logout") } -export interface APIKeyResponse { - key: string +export const getUser = async (): Promise => { + const response = await axios.get("/api/v2/users/me") + return response.data } -export const getApiKey = async (): Promise => { - const response = await axios.post("/api/v2/users/me/keys") +export const getApiKey = async (): Promise => { + const response = await axios.post("/api/v2/users/me/keys") return response.data } diff --git a/site/src/api/types.ts b/site/src/api/types.ts new file mode 100644 index 0000000000000..376ffbc7761ef --- /dev/null +++ b/site/src/api/types.ts @@ -0,0 +1,61 @@ +export interface LoginResponse { + session_token: string +} + +export interface UserResponse { + readonly id: string + readonly username: string + readonly email: string + readonly created_at: string +} + +/** + * `Organization` must be kept in sync with the go struct in organizations.go + */ +export interface Organization { + id: string + name: string + created_at: string + updated_at: string +} + +export interface Provisioner { + id: string + name: string +} + +// This must be kept in sync with the `Project` struct in the back-end +export interface Project { + id: string + created_at: string + updated_at: string + organization_id: string + name: string + provisioner: string + active_version_id: string +} + +export interface CreateProjectRequest { + name: string + organizationId: string + provisioner: string +} + +export interface CreateWorkspaceRequest { + name: string + project_id: string +} + +// Must be kept in sync with backend Workspace struct +export interface Workspace { + id: string + created_at: string + updated_at: string + owner_id: string + project_id: string + name: string +} + +export interface APIKeyResponse { + key: string +} diff --git a/site/src/app.tsx b/site/src/app.tsx index 0178b992b2018..e375f60d5810f 100644 --- a/site/src/app.tsx +++ b/site/src/app.tsx @@ -2,7 +2,6 @@ import React from "react" import CssBaseline from "@material-ui/core/CssBaseline" import ThemeProvider from "@material-ui/styles/ThemeProvider" import { SWRConfig } from "swr" -import { UserProvider } from "./contexts/UserContext" import { light } from "./theme" import { BrowserRouter as Router, Route, Routes } from "react-router-dom" @@ -15,6 +14,8 @@ import { ProjectPage } from "./pages/projects/[organization]/[project]" import { CreateWorkspacePage } from "./pages/projects/[organization]/[project]/create" import { WorkspacePage } from "./pages/workspaces/[workspace]" import { HealthzPage } from "./pages/healthz" +import { AuthAndNav, RequireAuth } from "./components/Page" +import { XServiceProvider } from "./xServices/StateContext" export const App: React.FC = () => { return ( @@ -37,28 +38,63 @@ export const App: React.FC = () => { }, }} > - + - } /> + + + + } + /> } /> } /> } /> - } /> + + + + } + /> - } /> - } /> + + + + } + /> + + + + } + /> - } /> + + + + } + /> {/* Using path="*"" means "match anything", so this route @@ -68,7 +104,7 @@ export const App: React.FC = () => { - + ) diff --git a/site/src/components/Navbar/NavbarView.stories.tsx b/site/src/components/Navbar/NavbarView.stories.tsx new file mode 100644 index 0000000000000..d6e9e5d30493b --- /dev/null +++ b/site/src/components/Navbar/NavbarView.stories.tsx @@ -0,0 +1,21 @@ +import { Story } from "@storybook/react" +import React from "react" +import { NavbarView, NavbarViewProps } from "./NavbarView" + +export default { + title: "Page/NavbarView", + component: NavbarView, + argTypes: { + onSignOut: { action: "Sign Out" }, + }, +} + +const Template: Story = (args: NavbarViewProps) => + +export const Primary = Template.bind({}) +Primary.args = { + user: { id: "1", username: "CathyCoder", email: "cathy@coder.com", created_at: "dawn" }, + onSignOut: () => { + return Promise.resolve() + }, +} diff --git a/site/src/components/Navbar/index.test.tsx b/site/src/components/Navbar/NavbarView.test.tsx similarity index 64% rename from site/src/components/Navbar/index.test.tsx rename to site/src/components/Navbar/NavbarView.test.tsx index a4d794cf7847d..cc1089cb1dd78 100644 --- a/site/src/components/Navbar/index.test.tsx +++ b/site/src/components/Navbar/NavbarView.test.tsx @@ -1,16 +1,17 @@ import React from "react" import { screen } from "@testing-library/react" -import { render, MockUser } from "../../test_helpers" -import { Navbar } from "./index" +import { render } from "../../test_helpers" +import { MockUser } from "../../test_helpers/entities" +import { NavbarView } from "./NavbarView" -describe("Navbar", () => { +describe("NavbarView", () => { const noop = () => { return } it("renders content", async () => { // When - render() + render() // Then await screen.findAllByText("Coder", { exact: false }) @@ -24,7 +25,7 @@ describe("Navbar", () => { } // When - render() + render() // Then // There should be a 'B' avatar! diff --git a/site/src/components/Navbar/NavbarView.tsx b/site/src/components/Navbar/NavbarView.tsx new file mode 100644 index 0000000000000..b6e05c2b10c1e --- /dev/null +++ b/site/src/components/Navbar/NavbarView.tsx @@ -0,0 +1,69 @@ +import React from "react" +import Button from "@material-ui/core/Button" +import { makeStyles } from "@material-ui/core/styles" +import { Link } from "react-router-dom" +import { Logo } from "../Icons" +import { UserDropdown } from "./UserDropdown" +import { UserResponse } from "../../api/types" + +export interface NavbarViewProps { + user?: UserResponse + onSignOut: () => void +} + +export const NavbarView: React.FC = ({ user, onSignOut }) => { + const styles = useStyles() + return ( +
+
+ + + +
+
+
{user && }
+
+ ) +} + +const useStyles = makeStyles((theme) => ({ + root: { + position: "relative", + display: "flex", + flex: "0", + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + height: "56px", + background: theme.palette.navbar.main, + marginTop: 0, + transition: "margin 150ms ease", + "@media (display-mode: standalone)": { + borderTop: `1px solid ${theme.palette.divider}`, + }, + borderBottom: `1px solid #383838`, + }, + fixed: { + flex: "0", + }, + fullWidth: { + flex: "1", + }, + logo: { + flex: "0", + height: "56px", + paddingLeft: theme.spacing(4), + paddingRight: theme.spacing(2), + borderRadius: 0, + "& svg": { + display: "block", + width: 125, + }, + }, + title: { + flex: "1", + textAlign: "center", + }, +})) diff --git a/site/src/components/Navbar/UserDropdown.tsx b/site/src/components/Navbar/UserDropdown.tsx index 164d5822651df..cfd100cd36965 100644 --- a/site/src/components/Navbar/UserDropdown.tsx +++ b/site/src/components/Navbar/UserDropdown.tsx @@ -11,11 +11,11 @@ import { LogoutIcon } from "../Icons" import { BorderedMenu } from "./BorderedMenu" import { UserProfileCard } from "../User/UserProfileCard" -import { User } from "../../contexts/UserContext" import { UserAvatar } from "../User" +import { UserResponse } from "../../api/types" export interface UserDropdownProps { - user: User + user: UserResponse onSignOut: () => void } diff --git a/site/src/components/Navbar/index.tsx b/site/src/components/Navbar/index.tsx index cc02405a9b3cd..b1f751b4fa1f6 100644 --- a/site/src/components/Navbar/index.tsx +++ b/site/src/components/Navbar/index.tsx @@ -1,70 +1,13 @@ -import React from "react" -import Button from "@material-ui/core/Button" -import { makeStyles } from "@material-ui/core/styles" -import { Link } from "react-router-dom" - -import { User } from "../../contexts/UserContext" -import { Logo } from "../Icons" -import { UserDropdown } from "./UserDropdown" - -export interface NavbarProps { - user?: User - onSignOut: () => void +import React, { useContext } from "react" +import { useActor } from "@xstate/react" +import { NavbarView } from "./NavbarView" +import { XServiceContext } from "../../xServices/StateContext" + +export const Navbar: React.FC = () => { + const xServices = useContext(XServiceContext) + const [userState, userSend] = useActor(xServices.userXService) + const { me } = userState.context + const onSignOut = () => userSend("SIGN_OUT") + + return } - -export const Navbar: React.FC = ({ user, onSignOut }) => { - const styles = useStyles() - return ( -
-
- - - -
-
-
{user && }
-
- ) -} - -const useStyles = makeStyles((theme) => ({ - root: { - position: "relative", - display: "flex", - flex: "0", - flexDirection: "row", - justifyContent: "center", - alignItems: "center", - height: "56px", - background: theme.palette.navbar.main, - marginTop: 0, - transition: "margin 150ms ease", - "@media (display-mode: standalone)": { - borderTop: `1px solid ${theme.palette.divider}`, - }, - borderBottom: `1px solid #383838`, - }, - fixed: { - flex: "0", - }, - fullWidth: { - flex: "1", - }, - logo: { - flex: "0", - height: "56px", - paddingLeft: theme.spacing(4), - paddingRight: theme.spacing(2), - borderRadius: 0, - "& svg": { - display: "block", - width: 125, - }, - }, - title: { - flex: "1", - textAlign: "center", - }, -})) diff --git a/site/src/components/Page/AuthAndNav.tsx b/site/src/components/Page/AuthAndNav.tsx new file mode 100644 index 0000000000000..9d400828d78c1 --- /dev/null +++ b/site/src/components/Page/AuthAndNav.tsx @@ -0,0 +1,12 @@ +import React from "react" +import { Navbar } from "../Navbar" +import { RequireAuth, RequireAuthProps } from "./RequireAuth" + +export const AuthAndNav: React.FC = ({ children }) => ( + + <> + + {children} + + +) diff --git a/site/src/components/Page/RequireAuth.tsx b/site/src/components/Page/RequireAuth.tsx new file mode 100644 index 0000000000000..099713cc1d286 --- /dev/null +++ b/site/src/components/Page/RequireAuth.tsx @@ -0,0 +1,23 @@ +import { useActor } from "@xstate/react" +import React, { useContext } from "react" +import { Navigate, useLocation } from "react-router" +import { XServiceContext } from "../../xServices/StateContext" +import { FullScreenLoader } from "../Loader/FullScreenLoader" + +export interface RequireAuthProps { + children: JSX.Element +} + +export const RequireAuth: React.FC = ({ children }) => { + const xServices = useContext(XServiceContext) + const [userState] = useActor(xServices.userXService) + const location = useLocation() + + if (userState.matches("signedOut") || !userState.context.me) { + return + } else if (userState.hasTag("loading")) { + return + } else { + return children + } +} diff --git a/site/src/components/Page/index.tsx b/site/src/components/Page/index.tsx index a29a2e5d3927b..8232f4db244eb 100644 --- a/site/src/components/Page/index.tsx +++ b/site/src/components/Page/index.tsx @@ -1 +1,3 @@ export * from "./Footer" +export * from "./RequireAuth" +export * from "./AuthAndNav" diff --git a/site/src/components/SignIn/SignInForm.stories.tsx b/site/src/components/SignIn/SignInForm.stories.tsx index 5556cf38b6b6d..caac9bba5a230 100644 --- a/site/src/components/SignIn/SignInForm.stories.tsx +++ b/site/src/components/SignIn/SignInForm.stories.tsx @@ -1,16 +1,30 @@ import { Story } from "@storybook/react" import React from "react" -import { SignInForm, SignInProps } from "./SignInForm" +import { SignInForm, SignInFormProps } from "./SignInForm" export default { title: "SignIn/SignInForm", component: SignInForm, argTypes: { - loginHandler: { action: "Login" }, + isLoading: "boolean", + authErrorMessage: "string", + onSubmit: { action: "Submit" }, }, } -const Template: Story = (args) => +const Template: Story = (args: SignInFormProps) => -export const Example = Template.bind({}) -Example.args = {} +export const SignedOut = Template.bind({}) +SignedOut.args = { + isLoading: false, + authErrorMessage: undefined, + onSubmit: () => { + return Promise.resolve() + }, +} + +export const Loading = Template.bind({}) +Loading.args = { ...SignedOut.args, isLoading: true } + +export const WithError = Template.bind({}) +WithError.args = { ...SignedOut.args, authErrorMessage: "Email or password was invalid" } diff --git a/site/src/components/SignIn/SignInForm.test.tsx b/site/src/components/SignIn/SignInForm.test.tsx deleted file mode 100644 index 83694b50fced6..0000000000000 --- a/site/src/components/SignIn/SignInForm.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from "react" -import { act, fireEvent, screen, waitFor } from "@testing-library/react" -import { history, render } from "../../test_helpers" - -import { SignInForm } from "./SignInForm" - -describe("SignInForm", () => { - beforeEach(() => { - history.replace("/") - }) - - it("renders content", async () => { - // When - render() - - // Then - await screen.findByText("Sign In", { exact: false }) - }) - - it("shows an error message if SignIn fails", async () => { - // Given - const loginHandler = (_email: string, _password: string) => Promise.reject("Unacceptable credentials") - - // When - // Render the component - const { container } = render() - const inputs = container.querySelectorAll("input") - // Set username / password - fireEvent.change(inputs[0], { target: { value: "test@coder.com" } }) - fireEvent.change(inputs[1], { target: { value: "password" } }) - // Click sign-in - const elem = await screen.findByText("Sign In") - act(() => elem.click()) - - // Then - // Should see an error message - const errorMessage = await screen.findByText("The username or password is incorrect.") - expect(errorMessage).toBeDefined() - }) - - it("redirects when login is complete", async () => { - // Given - const loginHandler = (_email: string, _password: string) => Promise.resolve() - - // When - // Render the component - const { container } = render() - // Set user / password - const inputs = container.querySelectorAll("input") - fireEvent.change(inputs[0], { target: { value: "test@coder.com" } }) - fireEvent.change(inputs[1], { target: { value: "password" } }) - // Click sign-in - const elem = await screen.findByText("Sign In") - act(() => elem.click()) - - // Then - // Should redirect because login was successful - await waitFor(() => expect(history.location.pathname).toEqual("/")) - }) - - it("respects ?redirect query parameter when complete", async () => { - // Given - const loginHandler = (_email: string, _password: string) => Promise.resolve() - // Set a path to redirect to after login is successful - history.replace("/login?redirect=%2Fsome%2Fother%2Fpath") - - // When - // Render the component - const { container } = render() - // Set user / password - const inputs = container.querySelectorAll("input") - fireEvent.change(inputs[0], { target: { value: "test@coder.com" } }) - fireEvent.change(inputs[1], { target: { value: "password" } }) - // Click sign-in - const elem = await screen.findByText("Sign In") - act(() => elem.click()) - - // Then - // Should redirect to /some/other/path because ?redirect was specified and login was successful - await waitFor(() => expect(history.location.pathname).toEqual("/some/other/path")) - }) -}) diff --git a/site/src/components/SignIn/SignInForm.tsx b/site/src/components/SignIn/SignInForm.tsx index 6f48f61058d5e..3c52f3d936fd8 100644 --- a/site/src/components/SignIn/SignInForm.tsx +++ b/site/src/components/SignIn/SignInForm.tsx @@ -1,14 +1,11 @@ import { makeStyles } from "@material-ui/core/styles" import { FormikContextType, useFormik } from "formik" -import { Location } from "history" -import { useNavigate, useLocation } from "react-router-dom" import React from "react" -import { useSWRConfig } from "swr" import * as Yup from "yup" import { Welcome } from "./Welcome" import { FormTextField } from "../Form" -import * as API from "./../../api" +import FormHelperText from "@material-ui/core/FormHelperText" import { LoadingButton } from "./../Button" /** @@ -40,17 +37,14 @@ const useStyles = makeStyles((theme) => ({ }, })) -export interface SignInProps { - loginHandler?: (email: string, password: string) => Promise +export interface SignInFormProps { + isLoading: boolean + authErrorMessage?: string + onSubmit: ({ email, password }: { email: string; password: string }) => Promise } -export const SignInForm: React.FC = ({ - loginHandler = (email: string, password: string) => API.login(email, password), -}) => { - const navigate = useNavigate() - const location = useLocation() +export const SignInForm: React.FC = ({ isLoading, authErrorMessage, onSubmit }) => { const styles = useStyles() - const { mutate } = useSWRConfig() const form: FormikContextType = useFormik({ initialValues: { @@ -58,18 +52,7 @@ export const SignInForm: React.FC = ({ password: "", }, validationSchema, - onSubmit: async ({ email, password }, helpers) => { - try { - await loginHandler(email, password) - // Tell SWR to invalidate the cache for the user endpoint - await mutate("/api/v2/users/me") - - const redirect = getRedirectFromLocation(location) - await navigate(redirect) - } catch (err) { - helpers.setFieldError("password", "The username or password is incorrect.") - } - }, + onSubmit, }) return ( @@ -104,28 +87,25 @@ export const SignInForm: React.FC = ({ placeholder="Password" variant="outlined" /> + {authErrorMessage && ( + + {authErrorMessage} + + )}
- Sign In + {isLoading ? "" : "Sign In"}
) } - -const getRedirectFromLocation = (location: Location) => { - const defaultRedirect = "/" - - const searchParams = new URLSearchParams(location.search) - const redirect = searchParams.get("redirect") - return redirect ? redirect : defaultRedirect -} diff --git a/site/src/components/User/UserAvatar.tsx b/site/src/components/User/UserAvatar.tsx index 020e6eb063ad5..12070717908c9 100644 --- a/site/src/components/User/UserAvatar.tsx +++ b/site/src/components/User/UserAvatar.tsx @@ -1,9 +1,9 @@ import Avatar from "@material-ui/core/Avatar" import React from "react" -import { User } from "../../contexts/UserContext" +import { UserResponse } from "../../api/types" export interface UserAvatarProps { - user: User + user: UserResponse className?: string } diff --git a/site/src/components/User/UserProfileCard.tsx b/site/src/components/User/UserProfileCard.tsx index b3cc1ec3deeb9..882bea250cf43 100644 --- a/site/src/components/User/UserProfileCard.tsx +++ b/site/src/components/User/UserProfileCard.tsx @@ -1,12 +1,12 @@ import { makeStyles } from "@material-ui/core/styles" import Typography from "@material-ui/core/Typography" import React from "react" +import { UserResponse } from "../../api/types" -import { User } from "../../contexts/UserContext" import { UserAvatar } from "./UserAvatar" interface UserProfileCardProps { - user: User + user: UserResponse } export const UserProfileCard: React.FC = ({ user }) => { diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 8eb1887881795..466b7a6cb0a5c 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -6,13 +6,13 @@ import CloudCircleIcon from "@material-ui/icons/CloudCircle" import { Link } from "react-router-dom" import React from "react" import * as Constants from "./constants" -import * as API from "../../api" +import * as Types from "../../api/types" import { WorkspaceSection } from "./WorkspaceSection" export interface WorkspaceProps { - organization: API.Organization - workspace: API.Workspace - project: API.Project + organization: Types.Organization + workspace: Types.Workspace + project: Types.Project } /** diff --git a/site/src/contexts/UserContext.test.tsx b/site/src/contexts/UserContext.test.tsx deleted file mode 100644 index 58af022aa0fd1..0000000000000 --- a/site/src/contexts/UserContext.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from "react" -import { SWRConfig } from "swr" -import { screen, waitFor } from "@testing-library/react" -import { User, UserProvider, useUser } from "./UserContext" -import { history, MockUser, render } from "../test_helpers" - -namespace Helpers { - // Helper component that renders out the state of the `useUser` hook. - // It just renders simple text in the 'error', 'me', and 'loading' states, - // so that the test can get a peak at the state of the hook. - const TestComponent: React.FC<{ redirectOnFailure: boolean }> = ({ redirectOnFailure }) => { - const { me, error } = useUser(redirectOnFailure) - - if (error) { - return
{`Error: ${error.toString()}`}
- } - if (me) { - return
{`Me: ${me.toString()}`}
- } - - return
Loading
- } - - // Helper to render a userContext, and all the scaffolding needed - // (an SWRConfig as well as a UserPRovider) - export const renderUserContext = ( - simulatedRequest: () => Promise, - redirectOnFailure: boolean, - ): React.ReactElement => { - return ( - // Set up an SWRConfig that works for testing - we'll simulate a request, - // and set up the cache to reset every test. - new Map(), - }} - > - - - - - ) - } -} - -describe("UserContext", () => { - const failingRequest = () => Promise.reject("Failed to load user") - const successfulRequest = () => Promise.resolve(MockUser) - - // Reset the router to '/' before every test - beforeEach(() => { - history.replace("/") - }) - - it("shouldn't redirect if user fails to load and redirectOnFailure is false", async () => { - // When - render(Helpers.renderUserContext(failingRequest, false)) - - // Then - // Verify we get an error message - await waitFor(() => { - expect(screen.queryByText("Error:", { exact: false })).toBeDefined() - }) - // ...and the route should be unchanged - expect(history.location.pathname).toEqual("/") - expect(history.location.search).toEqual("") - }) - - it("should redirect if user fails to load and redirectOnFailure is true", async () => { - // When - render(Helpers.renderUserContext(failingRequest, true)) - - // Then - // Verify we route to the login page - await waitFor(() => expect(history.location.pathname).toEqual("/login")) - await waitFor(() => expect(history.location.search).toEqual("?redirect=%2F")) - }) - - it("should not redirect if user loads and redirectOnFailure is true", async () => { - // When - render(Helpers.renderUserContext(successfulRequest, true)) - - // Then - // Verify the user is rendered - await waitFor(() => { - expect(screen.queryByText("Me:", { exact: false })).toBeDefined() - }) - // ...and the route should be unchanged - expect(history.location.pathname).toEqual("/") - expect(history.location.search).toEqual("") - }) -}) diff --git a/site/src/contexts/UserContext.tsx b/site/src/contexts/UserContext.tsx deleted file mode 100644 index 459af26595b56..0000000000000 --- a/site/src/contexts/UserContext.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useLocation, useNavigate } from "react-router-dom" -import React, { useContext, useEffect } from "react" -import useSWR from "swr" - -import * as API from "../api" - -export interface User { - readonly id: string - readonly username: string - readonly email: string - readonly created_at: string -} - -export interface UserContext { - readonly error?: Error - readonly me?: User - readonly signOut: () => Promise -} - -const UserContext = React.createContext({ - signOut: () => { - return Promise.reject("Sign out API not available") - }, -}) - -export const useUser = (redirectOnError = false): UserContext => { - const ctx = useContext(UserContext) - const navigate = useNavigate() - const { pathname } = useLocation() - - const requestError = ctx.error - useEffect(() => { - if (redirectOnError && requestError) { - navigate({ - pathname: "/login", - search: "?redirect=" + encodeURIComponent(pathname), - }) - } - // Disabling exhaustive deps here because it can cause an - // infinite useEffect loop. Should (hopefully) go away - // when we switch to an alternate routing strategy. - }, [redirectOnError, requestError]) // eslint-disable-line react-hooks/exhaustive-deps - - return ctx -} - -export const UserProvider: React.FC = (props) => { - const navigate = useNavigate() - const location = useLocation() - const { data, error, mutate } = useSWR("/api/v2/users/me") - - const signOut = async () => { - await API.logout() - // Tell SWR to invalidate the cache for the user endpoint - await mutate("/api/v2/users/me") - navigate({ - pathname: "/login", - search: "?redirect=" + encodeURIComponent(location.pathname), - }) - } - - return ( - - {props.children} - - ) -} diff --git a/site/src/forms/CreateProjectForm.tsx b/site/src/forms/CreateProjectForm.tsx index a930cf487e054..77aefacef7c55 100644 --- a/site/src/forms/CreateProjectForm.tsx +++ b/site/src/forms/CreateProjectForm.tsx @@ -13,7 +13,7 @@ import { FormCloseButton, } from "../components/Form" import { LoadingButton } from "../components/Button" -import { Organization, Project, Provisioner, CreateProjectRequest } from "./../api" +import { Organization, Project, Provisioner, CreateProjectRequest } from "../api/types" export interface CreateProjectFormProps { provisioners: Provisioner[] diff --git a/site/src/forms/CreateWorkspaceForm.tsx b/site/src/forms/CreateWorkspaceForm.tsx index 06ff82b144c6f..5d95e14498732 100644 --- a/site/src/forms/CreateWorkspaceForm.tsx +++ b/site/src/forms/CreateWorkspaceForm.tsx @@ -6,7 +6,7 @@ import * as Yup from "yup" import { FormCloseButton, FormTextField, FormTitle, FormSection } from "../components/Form" import { LoadingButton } from "../components/Button" -import { Project, Workspace, CreateWorkspaceRequest } from "../api" +import { Project, Workspace, CreateWorkspaceRequest } from "../api/types" export interface CreateWorkspaceForm { project: Project diff --git a/site/src/pages/cli-auth.tsx b/site/src/pages/cli-auth.tsx index d4a080affd5b1..26620a9d0bac3 100644 --- a/site/src/pages/cli-auth.tsx +++ b/site/src/pages/cli-auth.tsx @@ -1,13 +1,17 @@ import { makeStyles } from "@material-ui/core/styles" -import React, { useEffect, useState } from "react" +import React, { useContext, useEffect, useState } from "react" import { getApiKey } from "../api" import { CliAuthToken } from "../components/SignIn" import { FullScreenLoader } from "../components/Loader/FullScreenLoader" -import { useUser } from "../contexts/UserContext" +import { useActor } from "@xstate/react" +import { XServiceContext } from "../xServices/StateContext" export const CliAuthenticationPage: React.FC = () => { - const { me } = useUser(true) + const xServices = useContext(XServiceContext) + const [userState] = useActor(xServices.userXService) + const { me } = userState.context + const styles = useStyles() const [apiKey, setApiKey] = useState(null) diff --git a/site/src/pages/index.tsx b/site/src/pages/index.tsx index e6ae6e51ff2d3..970d010d60676 100644 --- a/site/src/pages/index.tsx +++ b/site/src/pages/index.tsx @@ -1,16 +1,7 @@ import React from "react" import { Navigate } from "react-router-dom" -import { FullScreenLoader } from "../components/Loader/FullScreenLoader" -import { useUser } from "../contexts/UserContext" export const IndexPage: React.FC = () => { - const { me } = useUser(/* redirectOnError */ true) - - if (me) { - // Once the user is logged in, just redirect them to /projects as the landing page - return - } - - return + return } diff --git a/site/src/pages/login.test.tsx b/site/src/pages/login.test.tsx new file mode 100644 index 0000000000000..7b2abb966d4c9 --- /dev/null +++ b/site/src/pages/login.test.tsx @@ -0,0 +1,52 @@ +import React from "react" +import { act, fireEvent, screen } from "@testing-library/react" +import { history, render } from "../test_helpers" +import { SignInPage } from "./login" +import { server } from "../test_helpers/server" +import { rest } from "msw" + +describe("SignInPage", () => { + beforeEach(() => { + history.replace("/login") + // appear logged out + server.use( + rest.get("/api/v2/users/me", (req, res, ctx) => { + return res(ctx.status(401), ctx.json({ message: "no user here" })) + }), + ) + }) + + it("renders the sign-in form", async () => { + // When + render() + + // Then + await screen.findByText("Sign In", { exact: false }) + }) + + it("shows an error message if SignIn fails", async () => { + // Given + const { container } = render() + // Make login fail + server.use( + rest.post("/api/v2/users/login", async (req, res, ctx) => { + return res(ctx.status(500), ctx.json({ message: "nope" })) + }), + ) + + // When + // Set username / password + const [username, password] = container.querySelectorAll("input") + fireEvent.change(username, { target: { value: "test@coder.com" } }) + fireEvent.change(password, { target: { value: "password" } }) + // Click sign-in + const signInButton = await screen.findByText("Sign In") + act(() => signInButton.click()) + + // Then + // Finding error by test id because it comes from the backend + const errorMessage = await screen.findByTestId("sign-in-error") + expect(errorMessage).toBeDefined() + expect(history.location.pathname).toEqual("/login") + }) +}) diff --git a/site/src/pages/login.tsx b/site/src/pages/login.tsx index eb404f04f5a01..f6f2c08f67917 100644 --- a/site/src/pages/login.tsx +++ b/site/src/pages/login.tsx @@ -1,6 +1,10 @@ import { makeStyles } from "@material-ui/core/styles" -import React from "react" +import { useActor } from "@xstate/react" +import React, { useContext } from "react" import { SignInForm } from "./../components/SignIn" +import { Navigate, useLocation } from "react-router-dom" +import { Location } from "history" +import { XServiceContext } from "../xServices/StateContext" export const useStyles = makeStyles((theme) => ({ root: { @@ -16,13 +20,36 @@ export const useStyles = makeStyles((theme) => ({ }, })) +const getRedirectFromLocation = (location: Location) => { + const defaultRedirect = "/" + + const searchParams = new URLSearchParams(location.search) + const redirect = searchParams.get("redirect") + return redirect ? redirect : defaultRedirect +} + export const SignInPage: React.FC = () => { const styles = useStyles() - return ( -
-
- + const location = useLocation() + const xServices = useContext(XServiceContext) + const [userState, userSend] = useActor(xServices.userXService) + const isLoading = userState.hasTag("loading") + const redirectTo = getRedirectFromLocation(location) + const authErrorMessage = userState.context.authError ? (userState.context.authError as Error).message : undefined + + const onSubmit = async ({ email, password }: { email: string; password: string }) => { + userSend({ type: "SIGN_IN", email, password }) + } + + if (userState.matches("signedIn")) { + return + } else { + return ( +
+
+ +
-
- ) + ) + } } diff --git a/site/src/pages/projects/[organization]/[project]/create.tsx b/site/src/pages/projects/[organization]/[project]/create.tsx index 545c9913c663a..87e93d36bb47a 100644 --- a/site/src/pages/projects/[organization]/[project]/create.tsx +++ b/site/src/pages/projects/[organization]/[project]/create.tsx @@ -3,8 +3,8 @@ import { makeStyles } from "@material-ui/core/styles" import { useNavigate, useParams } from "react-router-dom" import useSWR from "swr" +import * as Types from "../../../../api/types" import * as API from "../../../../api" -import { useUser } from "../../../../contexts/UserContext" import { ErrorSummary } from "../../../../components/ErrorSummary" import { FullScreenLoader } from "../../../../components/Loader/FullScreenLoader" import { CreateWorkspaceForm } from "../../../../forms/CreateWorkspaceForm" @@ -14,13 +14,12 @@ export const CreateWorkspacePage: React.FC = () => { const { organization: organizationName, project: projectName } = useParams() const navigate = useNavigate() const styles = useStyles() - const { me } = useUser(/* redirectOnError */ true) - const { data: organizationInfo, error: organizationError } = useSWR( + const { data: organizationInfo, error: organizationError } = useSWR( () => `/api/v2/users/me/organizations/${organizationName}`, ) - const { data: project, error: projectError } = useSWR(() => { + const { data: project, error: projectError } = useSWR(() => { return `/api/v2/organizations/${unsafeSWRArgument(organizationInfo).id}/projects/${projectName}` }) @@ -28,7 +27,7 @@ export const CreateWorkspacePage: React.FC = () => { navigate(`/projects/${organizationName}/${projectName}`) }, [navigate, organizationName, projectName]) - const onSubmit = async (req: API.CreateWorkspaceRequest) => { + const onSubmit = async (req: Types.CreateWorkspaceRequest) => { const workspace = await API.Workspace.create(req) navigate(`/workspaces/${workspace.id}`) return workspace @@ -42,7 +41,7 @@ export const CreateWorkspacePage: React.FC = () => { return } - if (!me || !project) { + if (!project) { return } diff --git a/site/src/pages/projects/[organization]/[project]/index.tsx b/site/src/pages/projects/[organization]/[project]/index.tsx index 858a6bb87a565..b762335bce754 100644 --- a/site/src/pages/projects/[organization]/[project]/index.tsx +++ b/site/src/pages/projects/[organization]/[project]/index.tsx @@ -4,13 +4,11 @@ import Paper from "@material-ui/core/Paper" import { Link, useNavigate, useParams } from "react-router-dom" import useSWR from "swr" -import { Organization, Project, Workspace } from "../../../../api" +import { Organization, Project, Workspace } from "../../../../api/types" import { Header } from "../../../../components/Header" import { FullScreenLoader } from "../../../../components/Loader/FullScreenLoader" -import { Navbar } from "../../../../components/Navbar" import { Footer } from "../../../../components/Page" import { Column, Table } from "../../../../components/Table" -import { useUser } from "../../../../contexts/UserContext" import { ErrorSummary } from "../../../../components/ErrorSummary" import { firstOrItem } from "../../../../util/array" import { EmptyState } from "../../../../components/EmptyState" @@ -18,7 +16,6 @@ import { unsafeSWRArgument } from "../../../../util" export const ProjectPage: React.FC = () => { const styles = useStyles() - const { me, signOut } = useUser(true) const navigate = useNavigate() const { project: projectName, organization: organizationName } = useParams() @@ -47,7 +44,7 @@ export const ProjectPage: React.FC = () => { return } - if (!me || !projectInfo || !workspaces) { + if (!projectInfo || !workspaces) { return } @@ -89,7 +86,6 @@ export const ProjectPage: React.FC = () => { return (
-
{ const styles = useStyles() - const { me, signOut } = useUser(true) const { data: orgs, error: orgsError } = useSWR("/api/v2/users/me/organizations") const { data: projects, error } = useSWR( orgs ? `/api/v2/organizations/${orgs[0].id}/projects` : null, @@ -31,7 +28,7 @@ export const ProjectsPage: React.FC = () => { return } - if (!me || !projects || !orgs) { + if (!projects || !orgs) { return } @@ -74,7 +71,6 @@ export const ProjectsPage: React.FC = () => { return (
-
diff --git a/site/src/pages/workspaces/[workspace].tsx b/site/src/pages/workspaces/[workspace].tsx index 86e3e0ba4cdbe..8e153eebb787c 100644 --- a/site/src/pages/workspaces/[workspace].tsx +++ b/site/src/pages/workspaces/[workspace].tsx @@ -2,33 +2,30 @@ import React from "react" import useSWR from "swr" import { makeStyles } from "@material-ui/core/styles" import { useParams } from "react-router-dom" -import { Navbar } from "../../components/Navbar" import { Footer } from "../../components/Page" -import { useUser } from "../../contexts/UserContext" import { firstOrItem } from "../../util/array" import { ErrorSummary } from "../../components/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" import { Workspace } from "../../components/Workspace" import { unsafeSWRArgument } from "../../util" -import * as API from "../../api" +import * as Types from "../../api/types" export const WorkspacePage: React.FC = () => { const styles = useStyles() const { workspace: workspaceQueryParam } = useParams() - const { me, signOut } = useUser(true) - const { data: workspace, error: workspaceError } = useSWR(() => { + const { data: workspace, error: workspaceError } = useSWR(() => { const workspaceParam = firstOrItem(workspaceQueryParam, null) return `/api/v2/workspaces/${workspaceParam}` }) // Fetch parent project - const { data: project, error: projectError } = useSWR(() => { + const { data: project, error: projectError } = useSWR(() => { return `/api/v2/projects/${unsafeSWRArgument(workspace).project_id}` }) - const { data: organization, error: organizationError } = useSWR(() => { + const { data: organization, error: organizationError } = useSWR(() => { return `/api/v2/organizations/${unsafeSWRArgument(project).organization_id}` }) @@ -44,14 +41,12 @@ export const WorkspacePage: React.FC = () => { return } - if (!me || !workspace || !project || !organization) { + if (!workspace || !project || !organization) { return } return (
- -
diff --git a/site/src/test_helpers/mocks.ts b/site/src/test_helpers/entities.ts similarity index 74% rename from site/src/test_helpers/mocks.ts rename to site/src/test_helpers/entities.ts index 081292b2d663e..7633161b4a23a 100644 --- a/site/src/test_helpers/mocks.ts +++ b/site/src/test_helpers/entities.ts @@ -1,7 +1,10 @@ -import { User } from "../contexts/UserContext" -import { Provisioner, Organization, Project, Workspace } from "../api" +import { Provisioner, Organization, Project, Workspace, UserResponse } from "../api/types" -export const MockUser: User = { +export const MockSessionToken = { session_token: "my-session-token" } + +export const MockAPIKey = { key: "my-api-key" } + +export const MockUser: UserResponse = { id: "test-user-id", username: "TestUser", email: "test@coder.com", diff --git a/site/src/test_helpers/handlers.ts b/site/src/test_helpers/handlers.ts new file mode 100644 index 0000000000000..c7ae6df9481d9 --- /dev/null +++ b/site/src/test_helpers/handlers.ts @@ -0,0 +1,20 @@ +import { rest } from "msw" +import * as M from "./entities" + +export const handlers = [ + rest.post("/api/v2/users/me/workspaces", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockWorkspace)) + }), + rest.post("/api/v2/users/login", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockSessionToken)) + }), + rest.post("/api/v2/users/logout", async (req, res, ctx) => { + return res(ctx.status(200)) + }), + rest.get("/api/v2/users/me", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockUser)) + }), + rest.get("/api/v2/users/me/keys", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockAPIKey)) + }), +] diff --git a/site/src/test_helpers/index.tsx b/site/src/test_helpers/index.tsx index 08626d442a0c4..ce192658cc757 100644 --- a/site/src/test_helpers/index.tsx +++ b/site/src/test_helpers/index.tsx @@ -5,13 +5,16 @@ import ThemeProvider from "@material-ui/styles/ThemeProvider" import { dark } from "../theme" import { createMemoryHistory } from "history" import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom" +import { XServiceProvider } from "../xServices/StateContext" export const history = createMemoryHistory() export const WrapperComponent: React.FC = ({ children }) => { return ( - {children} + + {children} + ) } @@ -20,4 +23,4 @@ export const render = (component: React.ReactElement): RenderResult => { return wrappedRender({component}) } -export * from "./mocks" +export * from "./entities" diff --git a/site/src/test_helpers/server.ts b/site/src/test_helpers/server.ts new file mode 100644 index 0000000000000..104074528453c --- /dev/null +++ b/site/src/test_helpers/server.ts @@ -0,0 +1,5 @@ +import { setupServer } from "msw/node" +import { handlers } from "./handlers" + +// This configures a request mocking server with the given request handlers. +export const server = setupServer(...handlers) diff --git a/site/src/xServices/StateContext.tsx b/site/src/xServices/StateContext.tsx new file mode 100644 index 0000000000000..6086a112e0815 --- /dev/null +++ b/site/src/xServices/StateContext.tsx @@ -0,0 +1,24 @@ +import React, { createContext } from "react" +import { useInterpret } from "@xstate/react" +import { ActorRefFrom } from "xstate" +import { userMachine } from "./user/userXService" + +interface XServiceContextType { + userXService: ActorRefFrom +} + +/** + * Consuming this Context will not automatically cause rerenders because + * the xServices in it are static references. + * + * To use one of the xServices, `useActor` will access all its state + * (causing re-renders for any changes to that one xService) and + * `useSelector` will access just one piece of state. + */ +export const XServiceContext = createContext({} as XServiceContextType) + +export const XServiceProvider: React.FC = ({ children }) => { + const userXService = useInterpret(userMachine, { devTools: true }) + + return {children} +} diff --git a/site/src/xServices/user/userXService.ts b/site/src/xServices/user/userXService.ts new file mode 100644 index 0000000000000..bcbbe12868418 --- /dev/null +++ b/site/src/xServices/user/userXService.ts @@ -0,0 +1,145 @@ +import { createMachine, assign } from "xstate" +import * as Types from "../../api/types" +import * as API from "../../api" + +export interface UserContext { + getUserError?: Error | unknown // unknown is a concession while I work out typing issues + authError?: Error | unknown + me?: Types.UserResponse +} + +export type UserEvent = { type: "SIGN_OUT" } | { type: "SIGN_IN"; email: string; password: string } + +export const userMachine = + /** @xstate-layout N4IgpgJg5mDOIC5QFdZgE4GUAuBDbYAdLAJZQB2kA8stgMSYCSA4gHID6jrioADgPalsJfuR4gAHogDM0gJyEATAHYAbNIAscxdunKArKo0AaEAE9EG+YX0BGABz3FD6atsbbc5QF9vp1Bg4+ESkFCTkUIzkdBCiROEAbvwA1iFk5FHiAkIiYkiSiHIADITSOvbSRRqq2m7StqYWCIr2+ja2tvVV+vqaRVW+-mhYeATE6eGR0Rjo-OiEvAA2+ABmcwC24xSZ+dkkwqLiUghlyoRF+opayq4X+sqK+o2Il6qEcq5y+hrfjt+qgxAARGwUIMGwwgiAFVhjE4oREikiOCALJgLKCfa5I4yIyEZTFZQaezFK5lPTPBC2IqKUqKC4EjqqKrFOSA4FBMbgyFQGEYOgzOYLZbYNboTao9G7TEHPKgY7SewlTQ3dRyVQ6GpVSmKMqEIz2ZRKjrKIqqJzs4actIUSBRBgsDhUKEAFQxOUO+WOhvshA0ahualsqnu8kpthUhGplVUIa+rUq9ktgVGNvIkxo9FilAR5CSqS25Ez7qxnvliCMGlK1Tk6vN5p+T3ML0Ub2U7iKXmcGg8tmTILGoXTEUzAvQs3mS1WG0LxelHrlBQQIbasc7aiKtnbV3sOpaUZXBjkwd1A0B5H4EDg4g5qcL1FoJdlOIQdlpH2qhmJzJ+DWbCB7WxSmUIl2wJM0CSTPwgStO8h0mHY+BlbEvReICaSMbQflkU0fkpHtfS3MC3C+aR9ENfR+2tMEwAhSY+XQJ8UPLACziVS5TXKAxPDI8MaSjIojSqPQezNRUqLg9I7UXPZn1Q5dqn1CMNEEi4HAecNIyVGoIweVQDH0tloNvUF4JHR951LRdvQcc4PCcAx12-fDOnOeRTU7C4SXsPtjNg4ImLLJddUIdiVBpOQKJ4psmh0ASQOPRUiI8RRfF8IA */ + createMachine( + { + tsTypes: {} as import("./userXService.typegen").Typegen0, + schema: { + context: {} as UserContext, + events: {} as UserEvent, + services: {} as { + getMe: { + data: Types.UserResponse + } + signIn: { + data: Types.LoginResponse | undefined + } + }, + }, + context: { + me: undefined, + getUserError: undefined, + authError: undefined, + }, + id: "userState", + initial: "gettingUser", + states: { + signedOut: { + on: { + SIGN_IN: { + target: "#userState.signingIn", + }, + }, + }, + signingIn: { + invoke: { + src: "signIn", + id: "signIn", + onDone: [ + { + target: "#userState.gettingUser", + actions: "clearAuthError", + }, + ], + onError: [ + { + actions: "assignAuthError", + target: "#userState.signedOut", + }, + ], + }, + tags: "loading", + }, + gettingUser: { + invoke: { + src: "getMe", + id: "getMe", + onDone: [ + { + actions: ["assignMe", "clearGetUserError"], + target: "#userState.signedIn", + }, + ], + onError: [ + { + actions: "assignGetUserError", + target: "#userState.signedOut", + }, + ], + }, + tags: "loading", + }, + signedIn: { + on: { + SIGN_OUT: { + target: "#userState.signingOut", + }, + }, + }, + signingOut: { + invoke: { + src: "signOut", + id: "signOut", + onDone: [ + { + actions: ["unassignMe", "clearAuthError"], + target: "#userState.signedOut", + }, + ], + onError: [ + { + actions: "assignAuthError", + target: "#userState.signedIn", + }, + ], + }, + tags: "loading", + }, + }, + }, + { + services: { + signIn: async (_, event: UserEvent) => { + if (event.type === "SIGN_IN") { + return await API.login(event.email, event.password) + } + }, + signOut: API.logout, + getMe: API.getUser, + }, + actions: { + assignMe: assign({ + me: (_, event) => event.data, + }), + unassignMe: assign((context: UserContext) => ({ + ...context, + me: undefined, + })), + assignGetUserError: assign({ + getUserError: (_, event) => event.data, + }), + clearGetUserError: assign((context: UserContext) => ({ + ...context, + getUserError: undefined, + })), + assignAuthError: assign({ + authError: (_, event) => event.data, + }), + clearAuthError: assign((context: UserContext) => ({ + ...context, + authError: undefined, + })), + }, + }, + ) diff --git a/site/yarn.lock b/site/yarn.lock index 39a8e904ab4d5..b6fe1d874f493 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -1658,6 +1658,26 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@mswjs/cookies@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.2.0.tgz#7ef2b5d7e444498bb27cf57720e61f76a4ce9f23" + integrity sha512-GTKYnIfXVP8GL8HRWrse+ujqDXCLKvu7+JoL6pvZFzS/d2i9pziByoWD69cOe35JNoSrx2DPNqrhUF+vgV3qUA== + dependencies: + "@types/set-cookie-parser" "^2.4.0" + set-cookie-parser "^2.4.6" + +"@mswjs/interceptors@^0.15.1": + version "0.15.1" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.15.1.tgz#4a0009f56e51bc2cd3176f1507065c7d2f6c0d5e" + integrity sha512-D5B+ZJNlfvBm6ZctAfRBdNJdCHYAe2Ix4My5qfbHV5WH+3lkt3mmsjiWJzEh5ZwGDauzY487TldI275If7DJVw== + dependencies: + "@open-draft/until" "^1.0.3" + "@xmldom/xmldom" "^0.7.5" + debug "^4.3.3" + headers-polyfill "^3.0.4" + outvariant "^1.2.1" + strict-event-emitter "^0.2.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1700,6 +1720,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@open-draft/until@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" + integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== + "@playwright/test@1.20.0": version "1.20.0" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.20.0.tgz#df5b1b45976d11c365e6cb60f8ec1ca7cccb84cf" @@ -2812,6 +2837,11 @@ dependencies: "@types/node" "*" +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + "@types/cookiejar@*": version "2.1.2" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" @@ -2920,7 +2950,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@27.4.1": +"@types/jest@^27.4.1": version "27.4.1" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d" integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw== @@ -2928,6 +2958,11 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" +"@types/js-levenshtein@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" + integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== + "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -3083,6 +3118,13 @@ "@types/mime" "^1" "@types/node" "*" +"@types/set-cookie-parser@^2.4.0": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz#b6a955219b54151bfebd4521170723df5e13caad" + integrity sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w== + dependencies: + "@types/node" "*" + "@types/sockjs@^0.3.33": version "0.3.33" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" @@ -3604,6 +3646,43 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe" integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw== +"@xmldom/xmldom@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" + integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== + +"@xstate/cli@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@xstate/cli/-/cli-0.1.4.tgz#5d909b980a5e62744f90b2790be3aa2717cfb4f8" + integrity sha512-+MkeFGi+gouY8o+/GWG7/62c4WZAtsMlABi5NFBN2owIbwohJQbflc9jBMzN+U3Ho0QGC2gHBwsretsSsNkuuA== + dependencies: + "@babel/core" "^7.12.10" + "@xstate/machine-extractor" "0.6.2" + "@xstate/tools-shared" "1.1.2" + chokidar "^3.5.3" + commander "^8.0.0" + xstate "^4.29.0" + +"@xstate/machine-extractor@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@xstate/machine-extractor/-/machine-extractor-0.6.2.tgz#2fe5edb6b965fd1f45fa68644a4ef69f125c4fc0" + integrity sha512-zyDrBMDCpPestEpnWHwmJ42qLIVOqRVUKa491kmdix/vT8z/3P3Ib6MOSbD8lp2yaF49kIUDUCCkvQA6TcLRyA== + +"@xstate/react@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@xstate/react/-/react-2.0.1.tgz#2b4717369d419e78a6c67f2dfcd1a3be9abce2d9" + integrity sha512-sT3hxyzNBw+bm7uT3BP+uXzN0MnRqiaj/U9Yl4OYaMAUJXWsRvSA/ipL7EDf0gVLRGrRhJTCsC0cjWaduAAqnw== + dependencies: + use-isomorphic-layout-effect "^1.0.0" + use-subscription "^1.3.0" + +"@xstate/tools-shared@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@xstate/tools-shared/-/tools-shared-1.1.2.tgz#84660e1ff9ba48612af2d471a21905705fa62e16" + integrity sha512-/A0/3vI2N9Rr3uWKGpUkIv1GhVvQFsXB+vbf8RuSCcj5lnztnC2XEKCqzkric+T3BBViGVXuY7eh8UZw8OVPDw== + dependencies: + "@xstate/machine-extractor" "0.6.2" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -4295,7 +4374,7 @@ base16@^1.0.0: resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA= -base64-js@^1.0.2: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -4352,6 +4431,15 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bluebird@^3.3.5, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -4570,6 +4658,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -4731,6 +4827,14 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== +chalk@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4748,7 +4852,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4776,6 +4880,11 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -4882,6 +4991,18 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + cli-table3@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" @@ -4891,6 +5012,11 @@ cli-table3@^0.6.1: optionalDependencies: colors "1.4.0" +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -4909,6 +5035,11 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" @@ -5036,7 +5167,7 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== -commander@8.3.0, commander@^8.3.0: +commander@8.3.0, commander@^8.0.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -5160,7 +5291,7 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.2: +cookie@0.4.2, cookie@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== @@ -5466,7 +5597,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.3, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@4, debug@4.3.3, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -5555,6 +5686,13 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -6349,7 +6487,7 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0, events@^3.2.0: +events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -6491,6 +6629,15 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" @@ -6629,6 +6776,13 @@ figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -7175,6 +7329,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graphql@^16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05" + integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A== + gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -7366,6 +7525,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +headers-polyfill@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-3.0.4.tgz#cd70c815a441dd882372fcd6eda212ce997c9b18" + integrity sha512-I1DOM1EdWYntdrnCvqQtcKwSSuiTzoqOExy4v1mdcFixFZABlWP4IPHdmoLtPda0abMHqDOY4H9svhQ10DFR4w== + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -7603,7 +7767,7 @@ hyphenate-style-name@^1.0.3: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== -iconv-lite@0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -7617,7 +7781,7 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" -ieee754@^1.1.4: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -7696,6 +7860,26 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +inquirer@^8.2.0: + version "8.2.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.1.tgz#e00022e3e8930a92662f760f020686530a84671d" + integrity sha512-pxhBaw9cyTFMjwKtkjePWDhvwzvrNGAw7En4hottzlPvz80GZaMZthdDU35aA6/f5FRZf3uhE057q8w1DE3V2g== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -7965,6 +8149,11 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3: resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" @@ -7975,6 +8164,11 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== +is-node-process@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.0.1.tgz#4fc7ac3a91e8aac58175fe0578abbc56f2831b23" + integrity sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ== + is-number-object@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" @@ -8099,6 +8293,11 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" @@ -8753,6 +8952,11 @@ jpeg-js@0.4.3: resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" @@ -9156,6 +9360,14 @@ lodash@^4.0.1, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -9580,6 +9792,31 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msw@^0.39.2: + version "0.39.2" + resolved "https://registry.yarnpkg.com/msw/-/msw-0.39.2.tgz#832e9274db62c43cb79854d5a69dce031c700de8" + integrity sha512-ju/HpqQpE4/qCxZ23t5Gaau0KREn4QuFzdG28nP1EpidMrymMJuIvNd32+2uGTGG031PMwrC41YW7vCxHOwyHA== + dependencies: + "@mswjs/cookies" "^0.2.0" + "@mswjs/interceptors" "^0.15.1" + "@open-draft/until" "^1.0.3" + "@types/cookie" "^0.4.1" + "@types/js-levenshtein" "^1.1.1" + chalk "4.1.1" + chokidar "^3.4.2" + cookie "^0.4.2" + graphql "^16.3.0" + headers-polyfill "^3.0.4" + inquirer "^8.2.0" + is-node-process "^1.0.1" + js-levenshtein "^1.1.6" + node-fetch "^2.6.7" + path-to-regexp "^6.2.0" + statuses "^2.0.0" + strict-event-emitter "^0.2.0" + type-fest "^1.2.2" + yargs "^17.3.1" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -9593,6 +9830,11 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + nan@^2.12.1: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" @@ -9670,7 +9912,7 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" -node-fetch@2.6.7, node-fetch@^2.6.1: +node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -9918,7 +10160,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -9971,11 +10213,36 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +outvariant@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.2.1.tgz#e630f6cdc1dbf398ed857e36f219de4a005ccd35" + integrity sha512-bcILvFkvpMXh66+Ubax/inxbKRyWTUiiFIW2DWkiS79wakrLGn3Ydy+GvukadiyfZjaL6C7YhIem4EZSM282wA== + overlayscrollbars@^1.13.1: version "1.13.1" resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz#0b840a88737f43a946b9d87875a2f9e421d0338a" @@ -10230,6 +10497,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" + integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg== + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -11026,7 +11298,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -11305,6 +11577,14 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -11367,6 +11647,11 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -11381,6 +11666,13 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rxjs@^7.5.5: + version "7.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -11597,6 +11889,11 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-cookie-parser@^2.4.6: + version "2.4.8" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" + integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -11950,6 +12247,11 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +statuses@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + store2@^2.12.0: version "2.13.1" resolved "https://registry.yarnpkg.com/store2/-/store2-2.13.1.tgz#fae7b5bb9d35fc53dc61cd262df3abb2f6e59022" @@ -11987,6 +12289,13 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +strict-event-emitter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz#78e2f75dc6ea502e5d8a877661065a1e2deedecd" + integrity sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w== + dependencies: + events "^3.3.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -12343,6 +12652,11 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -12365,6 +12679,13 @@ tinycolor2@^1.4.1: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -12536,7 +12857,7 @@ tslib@^1.10.0, tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.3.0: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -12592,6 +12913,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -12841,6 +13167,13 @@ use-latest@^1.0.0: dependencies: use-isomorphic-layout-effect "^1.0.0" +use-subscription@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" + integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA== + dependencies: + object-assign "^4.1.1" + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -13019,6 +13352,13 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + web-namespaces@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" @@ -13432,6 +13772,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xstate@^4.29.0, xstate@^4.30.6: + version "4.30.6" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.30.6.tgz#62b6dea37a500e0e1c0ff7c553a801eea5119554" + integrity sha512-V7liK1cjkZRh6R/MSneG8S5VLGRatpOUcnNieiYJX4LbwKi9eUVUH5V04ugJYVcJ+2oKDKvEFvzk0VnSC7lTag== + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -13467,6 +13812,11 @@ yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.7: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^21.0.0: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" @@ -13480,6 +13830,19 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.3.1: + version "17.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" + integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yauzl@2.10.0, yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"