diff --git a/package.json b/package.json index 8fb7cfdeb..a66aa4d9f 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,10 @@ "@tailwindcss/forms": "^0.5.3", "@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/typography": "^0.5.9", + "@testing-library/dom": "^9.2.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", "@types/debug": "^4.1.7", "@types/node": "^18.11.18", "@types/react": "^18.0.25", @@ -40,6 +42,7 @@ "debug": "^4.3.4", "history": "^5.3.0", "jsdom": "^21.1.0", + "msw": "^1.2.1", "postcss": "^8.4.21", "qrcode.react": "^3.1.0", "query-string": "^8.1.0", diff --git a/src/app/app.tsx b/src/app/app.tsx index d30933c03..5a9bd2466 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -9,7 +9,7 @@ import { ftuxRouter, router } from "./router"; import { selectOrigin } from "@app/env"; import { RouterProvider } from "react-router"; -const AppRouter = () => { +export const AppRouter = () => { const origin = useSelector(selectOrigin); return (
diff --git a/src/app/router.tsx b/src/app/router.tsx index a79a81d90..866c427cc 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -57,7 +57,12 @@ import { } from "@app/ui"; import { ReactRouterErrorElement } from "@app/ui/shared/error-boundary"; -const ftuxRoutes: RouteObject[] = [ +const errorPatch = (appRoute: RouteObject) => ({ + ...appRoute, + errorElement: , +}); + +export const ftuxRoutes: RouteObject[] = [ { path: routes.HOME_PATH, element: , @@ -188,9 +193,9 @@ const ftuxRoutes: RouteObject[] = [ path: "*", element: , }, -]; +].map(errorPatch); -const appRoutes: RouteObject[] = [ +export const appRoutes: RouteObject[] = [ { path: routes.HOME_PATH, element: , @@ -470,12 +475,7 @@ const appRoutes: RouteObject[] = [ path: "*", element: , }, -]; - -const errorPatch = (appRoute: RouteObject) => ({ - ...appRoute, - errorElement: , -}); +].map(errorPatch); -export const ftuxRouter = createBrowserRouter(ftuxRoutes.map(errorPatch)); -export const router = createBrowserRouter(appRoutes.map(errorPatch)); +export const ftuxRouter = createBrowserRouter(ftuxRoutes); +export const router = createBrowserRouter(appRoutes); diff --git a/src/app/test/create-project.test.tsx b/src/app/test/create-project.test.tsx new file mode 100644 index 000000000..2c9c2c8d8 --- /dev/null +++ b/src/app/test/create-project.test.tsx @@ -0,0 +1,84 @@ +import { act, fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import { setupAppIntegrationTest } from "@app/test"; + +describe("Create project flow", () => { + describe("existing user with ssh keys", () => { + it("should successfully provision resources within an environment", async () => { + const { App } = setupAppIntegrationTest({ + initEntries: ["/create"], + }); + render(); + + // deploy code landing page + const el = await screen.findByRole("button"); + expect(el.textContent).toEqual("Deploy your code"); + // go to next page + fireEvent.click(el); + + // create environment page + const nameInput = await screen.findByRole("textbox", { name: "name" }); + await act(async () => { + await userEvent.type(nameInput, "test-project"); + }); + + const btn = await screen.findByRole("button", { + name: /Create Environment/, + }); + // go to next page + fireEvent.click(btn); + + // push your code page + await screen.findByText(/Push your code to continue/); + + // settings page + await screen.findByText(/Review your Settings/); + + const banner = await screen.findByRole("status"); + expect(banner.textContent).toMatch(/Your code has a Dockerfile/); + + const dbBtn = await screen.findByRole("button", { + name: /New Database/, + }); + fireEvent.click(dbBtn); + + const dbSelector = await screen.findByRole("combobox"); + userEvent.selectOptions(dbSelector, "postgres:14"); + const dbEnvVar = await screen.findByRole("textbox", { name: "envvar" }); + expect(dbEnvVar).toHaveDisplayValue("DATABASE_URL"); + + const saveBtn = await screen.findByRole("button", { + name: /Save & Deploy/, + }); + + // go to next page + fireEvent.click(saveBtn); + + // status page + await screen.findByRole("button", { + name: "View Environment", + }); + const status = await screen.findByText(/Deployed today/); + expect(status).toBeInTheDocument(); + + await screen.findByText("Initial configuration"); + await screen.findByText("App deployment"); + await screen.findByText("Database provision test-app-1-postgres"); + let ops = await screen.findAllByText("DONE"); + expect(ops.length).toEqual(3); + + // create https endpoint + await screen.findByText("Which service needs an endpoint?"); + + const vhostSelector = await screen.findAllByRole("radio"); + fireEvent.click(vhostSelector[0]); + const httpBtn = await screen.findByText("Create endpoint"); + fireEvent.click(httpBtn); + + await screen.findByText("HTTPS endpoint provision"); + ops = await screen.findAllByText("DONE"); + expect(ops.length).toEqual(4); + }); + }); +}); diff --git a/src/deploy/app/index.ts b/src/deploy/app/index.ts index 11ece6e67..f57a2f6a0 100644 --- a/src/deploy/app/index.ts +++ b/src/deploy/app/index.ts @@ -22,12 +22,13 @@ import { } from "../environment"; import { deserializeImage } from "../image"; import { deserializeDeployOperation, waitForOperation } from "../operation"; +import { DeployServiceResponse } from "../service"; import { selectDeploy } from "../slice"; export * from "./utils"; export interface DeployAppResponse { - id: string; + id: number; handle: string; git_repo: string; created_at: string; @@ -40,13 +41,43 @@ export interface DeployAppResponse { }; _embedded: { // TODO: fill in - services: { id: number }[]; + services: DeployServiceResponse[]; current_image: any; last_deploy_operation: any; last_operation: any; }; + _type: "app"; } +export const defaultAppResponse = ( + p: Partial = {}, +): DeployAppResponse => { + const now = new Date().toISOString(); + return { + id: 1, + handle: "", + git_repo: "", + created_at: now, + updated_at: now, + deployment_method: "", + status: "provisioned", + _links: { + account: { href: "" }, + current_configuration: { href: "" }, + ...p._links, + }, + _embedded: { + services: [], + current_image: null, + last_deploy_operation: null, + last_operation: null, + ...p._embedded, + }, + ...p, + _type: "app", + }; +}; + export const deserializeDeployApp = (payload: DeployAppResponse): DeployApp => { const serviceIds: string[] = payload._embedded.services.map((s) => `${s.id}`); const links = payload._links; diff --git a/src/deploy/code-scan-result/index.ts b/src/deploy/code-scan-result/index.ts index 29ebde111..0cbc3a4e7 100644 --- a/src/deploy/code-scan-result/index.ts +++ b/src/deploy/code-scan-result/index.ts @@ -7,13 +7,29 @@ export interface DeployCodeScanResponse { dockerfile_present: boolean; procfile_present: boolean; _links: { - self: LinkResponse; app: LinkResponse; operation: LinkResponse; }; _type: "code_scan_result"; } +export const defaultCodeScanResponse = ( + c: Partial = {}, +): DeployCodeScanResponse => { + return { + id: 0, + aptible_yml_present: false, + dockerfile_present: false, + procfile_present: false, + _links: { + app: { href: "" }, + operation: { href: "" }, + }, + _type: "code_scan_result", + ...c, + }; +}; + export const fetchCodeScanResult = api.get<{ id: string }>( "/code_scan_results/:id", api.cache(), diff --git a/src/deploy/database-images/index.ts b/src/deploy/database-images/index.ts index d840ac5f8..4167775e7 100644 --- a/src/deploy/database-images/index.ts +++ b/src/deploy/database-images/index.ts @@ -20,6 +20,26 @@ export interface DeployDatabaseImageResponse { _type: "database_image"; } +export const defaultDatabaseImageResponse = ( + i: Partial = {}, +): DeployDatabaseImageResponse => { + const now = new Date().toISOString(); + return { + id: 0, + default: true, + description: "", + discoverable: true, + docker_repo: "", + type: "", + version: "", + visible: true, + created_at: now, + updated_at: now, + _type: "database_image", + ...i, + }; +}; + export const deserializeDeployDatabaseImage = ( payload: DeployDatabaseImageResponse, ): DeployDatabaseImage => { diff --git a/src/deploy/database/index.ts b/src/deploy/database/index.ts index 0aa869ede..ed64cd0e9 100644 --- a/src/deploy/database/index.ts +++ b/src/deploy/database/index.ts @@ -65,6 +65,7 @@ export interface DeployDatabaseResponse { disk: any; last_operation: any; }; + _type: "database"; } export interface BackupResponse { @@ -85,6 +86,38 @@ export interface HalBackups { backups: BackupResponse[]; } +export const defaultDatabaseResponse = ( + d: Partial = {}, +): DeployDatabaseResponse => { + const now = new Date().toISOString(); + return { + id: 1, + handle: "", + provisioned: true, + type: "", + status: "provisioned", + docker_repo: "", + current_kms_arn: "", + connection_url: "", + created_at: now, + updated_at: now, + _links: { + account: { href: "" }, + service: { href: "" }, + database_image: { href: "" }, + initialize_from: { href: "" }, + ...d._links, + }, + _embedded: { + disk: null, + last_operation: null, + ...d._embedded, + }, + _type: "database", + ...d, + }; +}; + export const deserializeDeployDatabase = ( payload: DeployDatabaseResponse, ): DeployDatabase => { diff --git a/src/deploy/endpoint/index.ts b/src/deploy/endpoint/index.ts index 76e1fe135..fdd2f0677 100644 --- a/src/deploy/endpoint/index.ts +++ b/src/deploy/endpoint/index.ts @@ -17,6 +17,8 @@ import type { AppState, DeployEndpoint, DeployOperationResponse, + LinkResponse, + ProvisionableStatus, } from "@app/types"; import { createSelector } from "@reduxjs/toolkit"; @@ -24,7 +26,75 @@ import { selectAppById, selectAppsByEnvId } from "../app"; import { selectDatabasesByEnvId } from "../database"; import { selectDeploy } from "../slice"; -export const deserializeDeployEndpoint = (payload: any): DeployEndpoint => { +interface DeployEndpointResponse { + id: number; + acme: boolean; + acme_configuration: string; + acme_dns_challenge_host: string; + acme_status: string; + container_exposed_ports: string[]; + container_port: string; + container_ports: string[]; + default: boolean; + docker_name: string; + external_host: string; + external_http_port: string; + external_https_port: string; + internal: boolean; + ip_whitelist: string[]; + platform: "alb" | "elb"; + type: string; + user_domain: string; + virtual_domain: string; + status: ProvisionableStatus; + created_at: string; + updated_at: string; + _links: { + service: LinkResponse; + certificate: LinkResponse; + }; + _type: "vhost"; +} + +export const defaultEndpointResponse = ( + resp: Partial = {}, +): DeployEndpointResponse => { + const now = new Date().toISOString(); + return { + id: 0, + acme: false, + acme_configuration: "", + acme_status: "", + acme_dns_challenge_host: "", + container_exposed_ports: [], + container_port: "", + container_ports: [], + default: true, + docker_name: "", + external_host: "", + external_http_port: "", + external_https_port: "", + internal: false, + ip_whitelist: [], + platform: "elb", + type: "", + user_domain: "", + virtual_domain: "", + status: "unknown", + created_at: now, + updated_at: now, + _links: { + service: { href: "" }, + certificate: { href: "" }, + }, + _type: "vhost", + ...resp, + }; +}; + +export const deserializeDeployEndpoint = ( + payload: DeployEndpointResponse, +): DeployEndpoint => { return { id: `${payload.id}`, acme: payload.acme, diff --git a/src/deploy/environment/index.ts b/src/deploy/environment/index.ts index 52fce9942..e3a9a8715 100644 --- a/src/deploy/environment/index.ts +++ b/src/deploy/environment/index.ts @@ -25,7 +25,7 @@ import { selectDeploy } from "../slice"; import { selectStackById } from "../stack"; interface DeployEnvironmentResponse { - id: string; + id: number; handle: string; created_at: string; updated_at: string; @@ -45,8 +45,40 @@ interface DeployEnvironmentResponse { environment: LinkResponse; stack: LinkResponse; }; + _type: "account"; } +export const defaultEnvResponse = ( + e: Partial = {}, +): DeployEnvironmentResponse => { + const now = new Date().toISOString(); + return { + id: 1, + handle: "", + created_at: now, + updated_at: now, + type: "development", + activated: true, + container_count: 0, + domain_count: 0, + total_disk_size: 0, + total_app_count: 0, + app_container_count: 0, + database_container_count: 0, + total_database_count: 0, + sweetness_stack: "", + total_backup_size: 0, + onboarding_status: "unknown", + _links: { + environment: { href: "" }, + stack: { href: "" }, + ...e._links, + }, + _type: "account", + ...e, + }; +}; + export const deserializeDeployEnvironment = ( payload: DeployEnvironmentResponse, ): DeployEnvironment => ({ diff --git a/src/deploy/operation/index.ts b/src/deploy/operation/index.ts index 0d90aef37..6adf48516 100644 --- a/src/deploy/operation/index.ts +++ b/src/deploy/operation/index.ts @@ -54,8 +54,51 @@ export interface DeployOperationResponse { ssh_portal_connections: LinkResponse; user: LinkResponse; }; + _type: "operation"; } +export const defaultOperationResponse = ( + op: Partial = {}, +): DeployOperationResponse => { + const now = new Date().toISOString(); + return { + id: 0, + type: "", + status: "unknown", + created_at: now, + updated_at: now, + git_ref: "", + docker_ref: "", + container_count: 0, + encrypted_env_json_new: "", + destination_region: "", + automated: false, + cancelled: false, + aborted: false, + immediate: false, + provisioned_iops: 0, + ebs_volume_type: "", + encrypted_stack_settings: "", + instance_profile: "", + user_name: "", + user_email: "", + env: "", + _links: { + account: { href: "" }, + code_scan_result: { href: "" }, + ephemeral_sessions: { href: "" }, + logs: { href: "" }, + resource: { href: "" }, + self: { href: "" }, + ssh_portal_connections: { href: "" }, + user: { href: "" }, + ...op._links, + }, + _type: "operation", + ...op, + }; +}; + export const defaultDeployOperation = ( op: Partial = {}, ): DeployOperation => { diff --git a/src/deploy/service/index.ts b/src/deploy/service/index.ts index cbfb09aa6..d7c3ac7da 100644 --- a/src/deploy/service/index.ts +++ b/src/deploy/service/index.ts @@ -12,8 +12,8 @@ import { selectDeploy } from "../slice"; export const DEFAULT_INSTANCE_CLASS: InstanceClass = "m4"; -interface DeployServiceResponse { - id: string; +export interface DeployServiceResponse { + id: number; handle: string; created_at: string; updated_at: string; @@ -24,8 +24,30 @@ interface DeployServiceResponse { container_count: number; container_memory_limit_mb: number; instance_class: InstanceClass; + _type: "service"; } +export const defaultServiceResponse = ( + s: Partial = {}, +): DeployServiceResponse => { + const now = new Date().toISOString(); + return { + id: 0, + handle: "", + docker_repo: "", + docker_ref: "", + process_type: "", + command: "", + container_count: 0, + container_memory_limit_mb: 0, + instance_class: "m4", + created_at: now, + updated_at: now, + _type: "service", + ...s, + }; +}; + export const deserializeDeployService = ( payload: DeployServiceResponse, ): DeployService => { diff --git a/src/deploy/stack/index.ts b/src/deploy/stack/index.ts index 0f8ccba01..5adf2a796 100644 --- a/src/deploy/stack/index.ts +++ b/src/deploy/stack/index.ts @@ -6,10 +6,64 @@ import { createTable, mustSelectEntity, } from "@app/slice-helpers"; -import type { AppState, DeployStack } from "@app/types"; +import type { AppState, DeployStack, LinkResponse } from "@app/types"; import { createSelector } from "@reduxjs/toolkit"; -export const deserializeDeployStack = (payload: any): DeployStack => { +interface DeployStackResponse { + id: number; + name: string; + region: string; + default: boolean; + public: boolean; + created_at: string; + updated_at: string; + outbound_ip_addresses: string[]; + memory_limits: boolean; + cpu_limits: boolean; + intrusion_detection: boolean; + expose_intrusion_detection_reports: boolean; + allow_t_instance_profile: boolean; + allow_c_instance_profile: boolean; + allow_m_instance_profile: boolean; + allow_r_instance_profile: boolean; + allow_granular_container_sizes: boolean; + _links: { + organization: LinkResponse; + }; + _type: "stack"; +} + +export const defaultStackResponse = ( + s: Partial = {}, +): DeployStackResponse => { + const now = new Date().toISOString(); + return { + id: 1, + name: "", + region: "", + default: true, + public: true, + created_at: now, + updated_at: now, + outbound_ip_addresses: [], + memory_limits: false, + cpu_limits: false, + intrusion_detection: false, + expose_intrusion_detection_reports: false, + allow_c_instance_profile: true, + allow_m_instance_profile: true, + allow_r_instance_profile: true, + allow_t_instance_profile: true, + allow_granular_container_sizes: true, + _links: { organization: { href: "" } }, + _type: "stack", + ...s, + }; +}; + +export const deserializeDeployStack = ( + payload: DeployStackResponse, +): DeployStack => { return { id: `${payload.id}`, name: payload.name, diff --git a/src/mocks/data.ts b/src/mocks/data.ts new file mode 100644 index 000000000..e1a4c9c53 --- /dev/null +++ b/src/mocks/data.ts @@ -0,0 +1,160 @@ +import { + defaultAppResponse, + defaultDatabaseImageResponse, + defaultEndpointResponse, + defaultEnvResponse, + defaultOperationResponse, + defaultServiceResponse, + defaultStackResponse, +} from "@app/deploy"; +import { defaultCodeScanResponse } from "@app/deploy/code-scan-result"; +import { createEnv } from "@app/env"; +import { defaultOrgResponse } from "@app/organizations"; +import { defaultSshKeyResponse } from "@app/ssh-keys"; +import { defaultTokenResponse } from "@app/token"; +import { defaultUserResponse } from "@app/users"; + +const idFactory = () => { + let id = 1; + return () => { + id += 1; + return id; + }; +}; +export const createId = idFactory(); +export const createText = (mixin: string, id: string | number = "1") => { + return `test-${mixin}-${id}`; +}; + +export const testEnv = createEnv({ + origin: "app", + authUrl: "https://auth.aptible.com", + apiUrl: "https://api.aptible.com", + billingUrl: "https://billing.aptible.com", + legacyDashboardUrl: "https://dashboard.aptible.com", +}); + +const testUserId = createId(); + +export const testToken = defaultTokenResponse({ + access_token: `${createId()}`, + id: `${createId()}`, + _links: { + self: { href: "" }, + user: { href: `${testEnv.authUrl}/users/${testUserId}` }, + actor: null, + }, +}); + +export const testUser = defaultUserResponse({ id: testUserId }); +export const testSshKey = defaultSshKeyResponse({ id: `${createId()}` }); + +export const testOrg = defaultOrgResponse({ + name: createText("org"), + id: `${createId()}`, +}); +export const testStack = defaultStackResponse({ + id: createId(), + name: createText("stack"), + region: "us-east-1", +}); + +export const testAccount = defaultEnvResponse({ + id: createId(), + handle: createText("account"), + _links: { + stack: { href: `${testEnv.apiUrl}/stacks/${testStack.id}` }, + environment: { href: "" }, + }, +}); + +export const testDatabaseId = createId(); + +export const testServiceRails = defaultServiceResponse({ + id: createId(), + handle: createText("rails s"), +}); +export const testServiceSidekiq = defaultServiceResponse({ + id: createId(), + handle: createText("rake sidekiq"), +}); + +export const testApp = defaultAppResponse({ + id: createId(), + handle: createText("app"), + _links: { + account: { href: `${testEnv.apiUrl}/accounts/${testAccount.id}` }, + current_configuration: { href: "" }, + }, + _embedded: { + current_image: null, + last_operation: null, + last_deploy_operation: null, + services: [testServiceRails, testServiceSidekiq], + }, +}); + +export const testScanOperation = defaultOperationResponse({ + id: createId(), + type: "scan_code", + status: "succeeded", + _links: { + code_scan_result: { + href: `${testEnv.apiUrl}/code_scan_results/${createId()}`, + }, + resource: { href: `${testEnv.apiUrl}/apps/${testApp.id}` }, + ephemeral_sessions: { href: "" }, + self: { href: "" }, + account: testApp._links.account, + ssh_portal_connections: { href: "" }, + user: { href: "" }, + logs: { href: "" }, + }, +}); + +export const testCodeScanResult = defaultCodeScanResponse({ + id: createId(), + dockerfile_present: true, + _links: { + app: { href: `${testEnv.apiUrl}/apps/${testApp.id}` }, + operation: { href: "" }, + }, +}); + +export const testPostgresDatabaseImage = defaultDatabaseImageResponse({ + id: createId(), + type: "postgres", + version: "14", +}); + +export const testRedisDatabaseImage = defaultDatabaseImageResponse({ + id: createId(), + type: "redis", + version: "5", +}); + +export const testDatabaseOp = defaultOperationResponse({ + id: createId(), + type: "provision", + status: "succeeded", + _links: { + resource: { + href: `${testEnv.apiUrl}/databases/${testDatabaseId}`, + }, + account: { href: `${testEnv.apiUrl}/accounts/${testAccount.id}` }, + code_scan_result: { href: "" }, + self: { href: "" }, + ssh_portal_connections: { href: "" }, + ephemeral_sessions: { href: "" }, + logs: { href: "" }, + user: { href: "" }, + }, +}); + +export const testEndpoint = defaultEndpointResponse({ + id: createId(), + _links: { + service: { href: `${testEnv.apiUrl}/services/${testServiceRails.id}` }, + certificate: { href: "" }, + }, +}); diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts new file mode 100644 index 000000000..125f3ef81 --- /dev/null +++ b/src/mocks/handlers.ts @@ -0,0 +1,289 @@ +import { + createId, + testAccount, + testApp, + testCodeScanResult, + testDatabaseId, + testDatabaseOp, + testEndpoint, + testEnv, + testOrg, + testPostgresDatabaseImage, + testRedisDatabaseImage, + testScanOperation, + testSshKey, + testStack, + testToken, + testUser, +} from "./data"; +import { defaultDatabaseResponse, defaultOperationResponse } from "@app/deploy"; +import { RestRequest, rest } from "msw"; + +const isValidToken = ({ headers }: RestRequest) => { + const authorization = headers.get("authorization"); + return `Bearer ${testToken.access_token}` === authorization; +}; + +const authHandlers = [ + rest.get(`${testEnv.authUrl}/current_token`, (_, res, ctx) => { + return res(ctx.json(testToken)); + }), + rest.get(`${testEnv.authUrl}/organizations`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json({ _embedded: { organizations: [testOrg] } })); + }), + rest.get(`${testEnv.authUrl}/organizations/:orgId/users`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res(ctx.json({ _embedded: { users: [testUser] } })); + }), + rest.get(`${testEnv.authUrl}/users/:userId/ssh_keys`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res(ctx.json({ _embedded: { ssh_keys: [testSshKey] } })); + }), +]; + +const apiHandlers = [ + rest.get(`${testEnv.apiUrl}/stacks`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json({ _embedded: { stacks: [testStack] } })); + }), + rest.get(`${testEnv.apiUrl}/accounts`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json({ _embedded: { accounts: [] } })); + }), + rest.get(`${testEnv.apiUrl}/databases`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json({ _embedded: { databases: [] } })); + }), + rest.post( + `${testEnv.apiUrl}/databases/:id/operations`, + async (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json(testDatabaseOp)); + }, + ), + rest.get(`${testEnv.apiUrl}/apps`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json({ _embedded: { apps: [] } })); + }), + rest.get(`${testEnv.apiUrl}/apps/:id`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json(testApp)); + }), + rest.get(`${testEnv.apiUrl}/apps/:id/operations`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res( + ctx.json({ + _embedded: { operations: [testScanOperation] }, + }), + ); + }), + rest.post(`${testEnv.apiUrl}/apps/:id/operations`, async (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + const data = await req.json(); + return res( + ctx.json( + defaultOperationResponse({ + id: createId(), + type: data.type, + env: data.env, + status: "succeeded", + _links: { + resource: { href: `${testEnv.apiUrl}/apps/${req.params.id}` }, + account: testApp._links.account, + code_scan_result: { href: "" }, + self: { href: "" }, + ssh_portal_connections: { href: "" }, + ephemeral_sessions: { href: "" }, + logs: { href: "" }, + user: { href: "" }, + }, + }), + ), + ); + }), + rest.get(`${testEnv.apiUrl}/apps/:id/vhosts`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json({ _embedded: { vhosts: [] } })); + }), + rest.get( + `${testEnv.apiUrl}/apps/:id/service_definitions`, + (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json({ _embedded: { service_definitions: [] } })); + }, + ), + rest.post(`${testEnv.apiUrl}/accounts`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res(ctx.json(testAccount)); + }), + rest.get(`${testEnv.apiUrl}/accounts/:id`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res(ctx.json(testAccount)); + }), + rest.patch(`${testEnv.apiUrl}/accounts/:id`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res( + ctx.json({ + ...testAccount, + onboarding_status: req.headers.get("onboarding_status"), + }), + ); + }), + rest.get(`${testEnv.apiUrl}/accounts/:envId/databases`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res(ctx.json({ _embedded: { databases: [] } })); + }), + rest.post( + `${testEnv.apiUrl}/accounts/:envId/databases`, + async (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + const data = await req.json(); + return res( + ctx.json( + defaultDatabaseResponse({ + id: testDatabaseId, + handle: data.handle, + type: data.type, + _links: { + account: { + href: `${testEnv.apiUrl}/accounts/${data.account_id}`, + }, + initialize_from: { href: "" }, + database_image: { + href: `${testEnv.apiUrl}/database_images/${data.database_image_id}`, + }, + service: { href: "" }, + }, + }), + ), + ); + }, + ), + rest.get(`${testEnv.apiUrl}/accounts/:envId/operations`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res( + ctx.json({ + _embedded: { databases: [testScanOperation, testDatabaseOp] }, + }), + ); + }), + rest.post(`${testEnv.apiUrl}/accounts/:envId/apps`, async (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + const data = await req.json(); + return res( + ctx.json({ + ...testApp, + handle: data.handle, + _links: { + account: { href: `${testEnv.apiUrl}/accounts/${data.account_id}` }, + current_configuration: { href: "" }, + }, + }), + ); + }), + rest.get(`${testEnv.apiUrl}/code_scan_results/:id`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res(ctx.json(testCodeScanResult)); + }), + rest.get(`${testEnv.apiUrl}/database_images`, (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + return res( + ctx.json({ + _embedded: { + database_images: [testRedisDatabaseImage, testPostgresDatabaseImage], + }, + }), + ); + }), + rest.post(`${testEnv.apiUrl}/services/:id/vhosts`, async (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res(ctx.json(testEndpoint)); + }), + rest.post( + `${testEnv.apiUrl}/vhosts/:id/operations`, + async (req, res, ctx) => { + if (!isValidToken(req)) { + return res(ctx.status(401)); + } + + return res( + ctx.json( + defaultOperationResponse({ + id: createId(), + type: "provision", + status: "succeeded", + _links: { + resource: { href: `${testEnv.apiUrl}/vhosts/${testEndpoint.id}` }, + account: testApp._links.account, + code_scan_result: { href: "" }, + self: { href: "" }, + ssh_portal_connections: { href: "" }, + ephemeral_sessions: { href: "" }, + logs: { href: "" }, + user: { href: "" }, + }, + }), + ), + ); + }, + ), +]; + +export const handlers = [...authHandlers, ...apiHandlers]; diff --git a/src/mocks/index.ts b/src/mocks/index.ts new file mode 100644 index 000000000..1a240d2e4 --- /dev/null +++ b/src/mocks/index.ts @@ -0,0 +1,2 @@ +export * from "./server"; +export * from "./data"; diff --git a/src/mocks/server.ts b/src/mocks/server.ts new file mode 100644 index 000000000..2908a09be --- /dev/null +++ b/src/mocks/server.ts @@ -0,0 +1,6 @@ +import { handlers } from "./handlers"; +// src/mocks/server.js +import { setupServer } from "msw/node"; + +// This configures a request mocking server with the given request handlers. +export const server = setupServer(...handlers); diff --git a/src/organizations/index.ts b/src/organizations/index.ts index ffa9e0da0..17186b69b 100644 --- a/src/organizations/index.ts +++ b/src/organizations/index.ts @@ -42,6 +42,37 @@ interface OrganizationResponse { _type: "organization"; } +export const defaultOrgResponse = ( + o: Partial = {}, +): OrganizationResponse => { + const now = new Date().toISOString(); + return { + address: "", + city: "", + created_at: now, + updated_at: now, + emergency_phone: "", + id: "", + name: "", + ops_alert_email: "", + primary_phone: "", + security_alert_email: "", + state: "", + zip: "", + _links: { + billing_detail: { href: "" }, + invitations: { href: "" }, + roles: { href: "" }, + security_officer: { href: "" }, + self: { href: "" }, + users: { href: "" }, + ...o._links, + }, + _type: "organization", + ...o, + }; +}; + export const ORGANIZATIONS_NAME = "organizations"; export const ORGANIZATION_SELECTED_NAME = "organizationSelected"; diff --git a/src/projects/index.ts b/src/projects/index.ts index d2b12847b..de86ef448 100644 --- a/src/projects/index.ts +++ b/src/projects/index.ts @@ -79,7 +79,7 @@ export const createProject = thunks.create( } log(envCtx); - envId = envCtx.json.data.id; + envId = `${envCtx.json.data.id}`; } const appCtx = yield* call( diff --git a/src/ssh-keys/index.ts b/src/ssh-keys/index.ts index 4ff88854f..8a0b966fd 100644 --- a/src/ssh-keys/index.ts +++ b/src/ssh-keys/index.ts @@ -1,9 +1,40 @@ import { authApi } from "@app/api"; +import { HalEmbedded } from "@app/types"; -export const fetchSSHKeys = authApi.get<{ userId: string }>( - "/users/:userId/ssh_keys", - authApi.cache(), -); +interface SshKeyResponse { + id: string; + name: string; + md5_fingerprint: string; + public_key_fingerprint: string; + sha256_fingerprint: string; + ssh_public_key: string; + created_at: string; + updated_at: string; + _type: "ssh_key"; +} + +export const defaultSshKeyResponse = ( + s: Partial = {}, +): SshKeyResponse => { + const now = new Date().toISOString(); + return { + id: "", + name: "", + md5_fingerprint: "", + public_key_fingerprint: "", + sha256_fingerprint: "", + ssh_public_key: "", + created_at: now, + updated_at: now, + _type: "ssh_key", + ...s, + }; +}; + +export const fetchSSHKeys = authApi.get< + { userId: string }, + HalEmbedded<{ ssh_keys: SshKeyResponse[] }> +>("/users/:userId/ssh_keys", authApi.cache()); export const addSSHKey = authApi.post<{ name: string; diff --git a/src/test/index.tsx b/src/test/index.tsx index afe60716b..b446c5f9c 100644 --- a/src/test/index.tsx +++ b/src/test/index.tsx @@ -1,24 +1,87 @@ import { Provider } from "react-redux"; -import { Route, Routes } from "react-router"; +import { + Route, + RouteObject, + RouterProvider, + Routes, + createMemoryRouter, +} from "react-router"; import { MemoryRouter } from "react-router-dom"; -import { applyMiddleware, createStore } from "redux"; import { prepareStore } from "saga-query"; -import { reducers, sagas } from "@app/app"; +import { reducers, rootEntities, sagas } from "@app/app"; +import { ftuxRoutes } from "@app/app/router"; +import { bootup } from "@app/bootup"; +import { testEnv } from "@app/mocks"; import type { AppState } from "@app/types"; +import { configureStore } from "@reduxjs/toolkit"; +import { REHYDRATE } from "redux-persist"; + +export const setupTestStore = (initState: Partial = {}) => { + const middleware = []; + const prepared = prepareStore({ + reducers: reducers, + sagas: sagas, + }); + + middleware.push(...prepared.middleware); + + const store = configureStore({ + preloadedState: { ...initState, entities: rootEntities }, + reducer: prepared.reducer, + devTools: false, + middleware: middleware, + }); + + prepared.run(); + + return { store }; +}; + +/** + * This function helps simulate booting the entire app as if it were + * the browser. All of redux, redux-saga, and redux-persist are loaded + * and configured. + * + * We also dispatch the `booup()` saga which fetches a bunch of data. + */ +export const setupAppIntegrationTest = ( + { + routes = ftuxRoutes, + initState = {}, + initEntries = [], + }: Partial<{ + routes: RouteObject[]; + initState: Partial; + initEntries: string[]; + }> = { + routes: ftuxRoutes, + initState: {}, + initEntries: [], + }, +) => { + const router = createMemoryRouter(routes, { initialEntries: initEntries }); + const { store } = setupTestStore({ + ...initState, + env: testEnv, + }); + store.dispatch(bootup()); + store.dispatch({ type: REHYDRATE }); + const App = () => { + return ( + + + + ); + }; + return { store, router, App }; +}; export const setupIntegrationTest = ( initState: Partial = {}, path = "", ) => { - const prepared = prepareStore({ reducers, sagas }); - - const store = createStore( - prepared.reducer, - initState as AppState, - applyMiddleware(...prepared.middleware), - ); - prepared.run(); + const { store } = setupTestStore(initState); const TestProvider = ({ children }: { children: React.ReactNode }) => { return ( @@ -31,5 +94,5 @@ export const setupIntegrationTest = ( ); }; - return { store, TestProvider, history }; + return { store, TestProvider }; }; diff --git a/src/test/setup.ts b/src/test/setup.ts index d0de870dc..a540ff9e2 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1 +1,25 @@ -import "@testing-library/jest-dom"; +import { server } from "@app/mocks"; +import matchers, { + TestingLibraryMatchers, +} from "@testing-library/jest-dom/matchers"; +import { expect } from "vitest"; + +declare global { + namespace Vi { + interface JestAssertion + extends jest.Matchers, + TestingLibraryMatchers {} + } +} + +expect.extend(matchers); + +// Establish API mocking before all tests. +beforeAll(() => server.listen()); + +// 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()); diff --git a/src/token/index.ts b/src/token/index.ts index 8d7277204..754165b1c 100644 --- a/src/token/index.ts +++ b/src/token/index.ts @@ -39,6 +39,31 @@ export interface JWTToken { name: string; } +export const defaultTokenResponse = ( + t: Partial = {}, +): TokenSuccessResponse => { + const now = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(now.getDate() + 1); + return { + access_token: "", + created_at: now.toISOString(), + expires_at: tomorrow.toISOString(), + expires_in: tomorrow.toISOString(), + scope: "manage", + id: "", + token_type: "", + _links: { + self: { href: "" }, + user: { href: "" }, + actor: null, + ...t._links, + }, + _type: "token", + ...t, + }; +}; + export const defaultJWTToken = (t: Partial = {}): JWTToken => { return { id: "", diff --git a/src/types/helpers.ts b/src/types/helpers.ts index f94f2ad33..5051a33f3 100644 --- a/src/types/helpers.ts +++ b/src/types/helpers.ts @@ -3,6 +3,11 @@ import type { SagaIterator } from "saga-query"; // https://stackoverflow.com/a/47636222 export const excludesFalse = (n?: T): n is T => Boolean(n); +// https://stackoverflow.com/a/72311590 +export type DeepPartial = { + [K in keyof T]?: T[K] extends object ? DeepPartial : T[K]; +}; + export type ApiGen = SagaIterator; export interface Action { diff --git a/src/ui/pages/create-project-git.tsx b/src/ui/pages/create-project-git.tsx index 8b35f4ea2..2f48bf43d 100644 --- a/src/ui/pages/create-project-git.tsx +++ b/src/ui/pages/create-project-git.tsx @@ -1897,23 +1897,25 @@ const CreateEndpointView = ({ {services.map((service) => { return (
- onChange(service.id)} - disabled={!!serviceId} - /> - - {service.processType === "cmd" ? ( - "Docker CMD Service" - ) : ( - <> - {service.processType} {service.command} - - )} - +
); })} diff --git a/src/ui/shared/banner.tsx b/src/ui/shared/banner.tsx index feb3821d2..f46861d31 100644 --- a/src/ui/shared/banner.tsx +++ b/src/ui/shared/banner.tsx @@ -16,6 +16,7 @@ export const Banner = ({ return (
+ {isLoading ? "Loading ..." : children} ); @@ -83,7 +83,7 @@ export const ButtonLink = ({ buttonShapeStyle(size, shape), ); return ( - + {isLoading ? "Loading ..." : children} ); diff --git a/src/ui/shared/input.tsx b/src/ui/shared/input.tsx index 7ecad5a45..055cf6a8e 100644 --- a/src/ui/shared/input.tsx +++ b/src/ui/shared/input.tsx @@ -12,7 +12,9 @@ export const Input = forwardRef((props, ref) => { "appearance-none block px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm", props.className, ); - return ; + return ( + + ); }); interface InputSearchProps extends React.HTMLProps { diff --git a/src/users/serializers.ts b/src/users/serializers.ts index a70c286bd..f23eec41e 100644 --- a/src/users/serializers.ts +++ b/src/users/serializers.ts @@ -19,7 +19,7 @@ export const defaultUser = (u: Partial = {}): User => { export function deserializeUser(u: UserResponse): User { return { - id: u.id, + id: `${u.id}`, name: u.name, email: u.email, otpEnabled: u.otp_enabled, @@ -29,3 +29,33 @@ export function deserializeUser(u: UserResponse): User { currentOtpId: extractIdFromLink(u._links.current_otp_configuration), }; } + +export function defaultUserResponse( + u: Partial = {}, +): UserResponse { + const now = new Date().toISOString(); + return { + id: 0, + name: "", + email: "", + otp_enabled: false, + superuser: false, + username: "", + verified: false, + created_at: now, + updated_at: now, + public_key_fingerprint: null, + ssh_public_key: null, + _links: { + self: { href: "" }, + roles: { href: "" }, + email_verification_challenges: { href: "" }, + current_otp_configuration: { href: "" }, + ssh_keys: { href: "" }, + u2f_devices: { href: "" }, + ...u._links, + }, + _type: "user", + ...u, + }; +} diff --git a/src/users/types.ts b/src/users/types.ts index 37a4c7a5f..2e5baae02 100644 --- a/src/users/types.ts +++ b/src/users/types.ts @@ -3,7 +3,7 @@ import type { HalEmbedded, LinkResponse } from "@app/types"; export interface UserResponse { created_at: string; email: string; - id: string; + id: number; name: string; otp_enabled: boolean; public_key_fingerprint: null; @@ -20,6 +20,7 @@ export interface UserResponse { ssh_keys: LinkResponse; u2f_devices: LinkResponse; }; + _type: "user"; } export type UsersResponse = HalEmbedded<{ users: UserResponse[] }>; diff --git a/tsconfig.json b/tsconfig.json index 7ad3f719f..b6c33f4be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "baseUrl": ".", "target": "esnext", + "types": ["vitest/globals"], "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, diff --git a/vite.config.mjs b/vite.config.mjs index 2630c6c5e..2ce751c70 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -12,6 +12,7 @@ export default defineConfig(() => { test: { globals: true, environment: "jsdom", + setupFiles: ["./src/test/setup.ts"], }, }; }); diff --git a/yarn.lock b/yarn.lock index 3929fd257..755cc9a87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -613,6 +613,32 @@ __metadata: languageName: node linkType: hard +"@mswjs/cookies@npm:^0.2.2": + version: 0.2.2 + resolution: "@mswjs/cookies@npm:0.2.2" + dependencies: + "@types/set-cookie-parser": ^2.4.0 + set-cookie-parser: ^2.4.6 + checksum: 23b1ef56d57efcc1b44600076f531a1fb703855af342a31e01bad4adaf0dab51f6d3b5595a95a7988c3f612ba075835f9a06c52833205284d101eb9a51dd72b0 + languageName: node + linkType: hard + +"@mswjs/interceptors@npm:^0.17.5": + version: 0.17.9 + resolution: "@mswjs/interceptors@npm:0.17.9" + dependencies: + "@open-draft/until": ^1.0.3 + "@types/debug": ^4.1.7 + "@xmldom/xmldom": ^0.8.3 + debug: ^4.3.3 + headers-polyfill: ^3.1.0 + outvariant: ^1.2.1 + strict-event-emitter: ^0.2.4 + web-encoding: ^1.1.5 + checksum: 4df726cbee93d8baa54ead1ecb11e98124468659f51eb659ef8ead4aca7d6375198baf412ea17d4810fa5f1ee4fa53994702cb3b0b4f6f427a2f0fb890020192 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.4": version: 2.1.4 resolution: "@nodelib/fs.scandir@npm:2.1.4" @@ -660,6 +686,13 @@ __metadata: languageName: node linkType: hard +"@open-draft/until@npm:^1.0.3": + version: 1.0.3 + resolution: "@open-draft/until@npm:1.0.3" + checksum: 323e92ebef0150ed0f8caedc7d219b68cdc50784fa4eba0377eef93533d3f46514eb2400ced83dda8c51bddc3d2c7b8e9cf95e5ec85ab7f62dfc015d174f62f2 + languageName: node + linkType: hard + "@polka/url@npm:^1.0.0-next.20": version: 1.0.0-next.21 resolution: "@polka/url@npm:1.0.0-next.21" @@ -929,6 +962,22 @@ __metadata: languageName: node linkType: hard +"@testing-library/dom@npm:^9.2.0": + version: 9.2.0 + resolution: "@testing-library/dom@npm:9.2.0" + dependencies: + "@babel/code-frame": ^7.10.4 + "@babel/runtime": ^7.12.5 + "@types/aria-query": ^5.0.1 + aria-query: ^5.0.0 + chalk: ^4.1.0 + dom-accessibility-api: ^0.5.9 + lz-string: ^1.5.0 + pretty-format: ^27.0.2 + checksum: b145f43cd06ff083012cf2503aff6ccba97ff80715fcb106fe64af690f5536557bf24d37b97e8d685bbe3803d7f71d685ce71426cb1b9e250c3611e4372dcfa9 + languageName: node + linkType: hard + "@testing-library/jest-dom@npm:^5.16.5": version: 5.16.5 resolution: "@testing-library/jest-dom@npm:5.16.5" @@ -960,6 +1009,15 @@ __metadata: languageName: node linkType: hard +"@testing-library/user-event@npm:^14.4.3": + version: 14.4.3 + resolution: "@testing-library/user-event@npm:14.4.3" + peerDependencies: + "@testing-library/dom": ">=7.21.4" + checksum: 852c48ea6db1c9471b18276617c84fec4320771e466cd58339a732ca3fd73ad35e5a43ae14f51af51a8d0a150dcf60fcaab049ef367871207bea8f92c4b8195e + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -974,6 +1032,13 @@ __metadata: languageName: node linkType: hard +"@types/aria-query@npm:^5.0.1": + version: 5.0.1 + resolution: "@types/aria-query@npm:5.0.1" + checksum: 69fd7cceb6113ed370591aef04b3fd0742e9a1b06dd045c43531448847b85de181495e4566f98e776b37c422a12fd71866e0a1dfd904c5ec3f84d271682901de + languageName: node + linkType: hard + "@types/chai-subset@npm:^1.3.3": version: 1.3.3 resolution: "@types/chai-subset@npm:1.3.3" @@ -997,6 +1062,13 @@ __metadata: languageName: node linkType: hard +"@types/cookie@npm:^0.4.1": + version: 0.4.1 + resolution: "@types/cookie@npm:0.4.1" + checksum: 3275534ed69a76c68eb1a77d547d75f99fedc80befb75a3d1d03662fb08d697e6f8b1274e12af1a74c6896071b11510631ba891f64d30c78528d0ec45a9c1a18 + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.7 resolution: "@types/debug@npm:4.1.7" @@ -1051,6 +1123,13 @@ __metadata: languageName: node linkType: hard +"@types/js-levenshtein@npm:^1.1.1": + version: 1.1.1 + resolution: "@types/js-levenshtein@npm:1.1.1" + checksum: 1d1ff1ee2ad551909e47f3ce19fcf85b64dc5146d3b531c8d26fc775492d36e380b32cf5ef68ff301e812c3b00282f37aac579ebb44498b94baff0ace7509769 + languageName: node + linkType: hard + "@types/ms@npm:*": version: 0.7.31 resolution: "@types/ms@npm:0.7.31" @@ -1133,6 +1212,15 @@ __metadata: languageName: node linkType: hard +"@types/set-cookie-parser@npm:^2.4.0": + version: 2.4.2 + resolution: "@types/set-cookie-parser@npm:2.4.2" + dependencies: + "@types/node": "*" + checksum: c31bf04eb9620829dc3c91bced74ac934ad039d20d20893fb5acac0f08769cbd4eef3bf7502a0289c7be59c3e9cfa456147b4e88bff47dd1b9efb4995ba5d5a3 + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.1 resolution: "@types/stack-utils@npm:2.0.1" @@ -1244,6 +1332,20 @@ __metadata: languageName: node linkType: hard +"@xmldom/xmldom@npm:^0.8.3": + version: 0.8.7 + resolution: "@xmldom/xmldom@npm:0.8.7" + checksum: 593d4429c2281ee7799adcb6ff8604b68cf30ce0721537e3e380287b423e67c7ac197d90987f932b4fd3febc409ded8435706e7f90fbba6e22e08740477341d1 + languageName: node + linkType: hard + +"@zxing/text-encoding@npm:0.9.0": + version: 0.9.0 + resolution: "@zxing/text-encoding@npm:0.9.0" + checksum: c23b12aee7639382e4949961304a1294776afaffa40f579e09ffecd0e5e68cf26ef3edd75009de46da8a536e571448755ca68b3e2ea707d53793c0edb2e2c34a + languageName: node + linkType: hard + "abab@npm:^2.0.6": version: 2.0.6 resolution: "abab@npm:2.0.6" @@ -1341,6 +1443,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: ^0.21.3 + checksum: 93111c42189c0a6bed9cdb4d7f2829548e943827ee8479c74d6e0b22ee127b2a21d3f8b5ca57723b8ef78ce011fbfc2784350eb2bde3ccfccf2f575fa8489815 + languageName: node + linkType: hard + "ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" @@ -1364,7 +1475,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^4.1.0": +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" dependencies: @@ -1487,6 +1598,13 @@ __metadata: languageName: node linkType: hard +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -1494,6 +1612,17 @@ __metadata: languageName: node linkType: hard +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -1543,6 +1672,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.1.13 + checksum: e2cf8429e1c4c7b8cbd30834ac09bd61da46ce35f5c22a78e6c2f04497d6d25541b16881e30a019c6fd3154150650ccee27a308eff3e26229d788bbdeb08ab84 + languageName: node + linkType: hard + "cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" @@ -1622,6 +1761,16 @@ __metadata: languageName: node linkType: hard +"chalk@npm:4.1.1": + version: 4.1.1 + resolution: "chalk@npm:4.1.1" + dependencies: + ansi-styles: ^4.1.0 + supports-color: ^7.1.0 + checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed + languageName: node + linkType: hard + "chalk@npm:^2.0.0": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -1643,7 +1792,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.0": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.1": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -1653,6 +1802,13 @@ __metadata: languageName: node linkType: hard +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 6fd5da1f5d18ff5712c1e0aed41da200d7c51c28f11b36ee3c7b483f3696dabc08927fc6b227735eb8f0e1215c9a8abd8154637f3eff8cada5959df7f58b024d + languageName: node + linkType: hard + "check-error@npm:^1.0.2": version: 1.0.2 resolution: "check-error@npm:1.0.2" @@ -1660,7 +1816,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.5.3": +"chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -1707,6 +1863,22 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: ^3.1.0 + checksum: 2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0": + version: 2.8.0 + resolution: "cli-spinners@npm:2.8.0" + checksum: 42bc69127706144b83b25da27e0719bdd8294efe43018e1736928a8f78a26e8d2b4dcd39af4a6401526ca647e99e302ad2b29bf19e67d1db403b977aca6abeb7 + languageName: node + linkType: hard + "cli-truncate@npm:^3.1.0": version: 3.1.0 resolution: "cli-truncate@npm:3.1.0" @@ -1717,6 +1889,31 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-width@npm:3.0.0" + checksum: 4c94af3769367a70e11ed69aa6095f1c600c0ff510f3921ab4045af961820d57c0233acfa8b6396037391f31b4c397e1f614d234294f979ff61430a6c166c3f6 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + "cloud-ui@workspace:.": version: 0.0.0-use.local resolution: "cloud-ui@workspace:." @@ -1727,8 +1924,10 @@ __metadata: "@tailwindcss/forms": ^0.5.3 "@tailwindcss/line-clamp": ^0.4.2 "@tailwindcss/typography": ^0.5.9 + "@testing-library/dom": ^9.2.0 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 + "@testing-library/user-event": ^14.4.3 "@types/debug": ^4.1.7 "@types/node": ^18.11.18 "@types/react": ^18.0.25 @@ -1741,6 +1940,7 @@ __metadata: debug: ^4.3.4 history: ^5.3.0 jsdom: ^21.1.0 + msw: ^1.2.1 postcss: ^8.4.21 qrcode.react: ^3.1.0 query-string: ^8.1.0 @@ -1835,6 +2035,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.4.2": + version: 0.4.2 + resolution: "cookie@npm:0.4.2" + checksum: a00833c998bedf8e787b4c342defe5fa419abd96b32f4464f718b91022586b8f1bafbddd499288e75c037642493c83083da426c6a9080d309e3bd90fd11baa9b + languageName: node + linkType: hard + "cosmiconfig@npm:^7.0.0": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" @@ -1989,6 +2196,15 @@ __metadata: languageName: node linkType: hard +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": version: 1.1.4 resolution: "define-properties@npm:1.1.4" @@ -2308,6 +2524,13 @@ __metadata: languageName: node linkType: hard +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 + languageName: node + linkType: hard + "expect@npm:^29.0.0": version: 29.3.1 resolution: "expect@npm:29.3.1" @@ -2321,6 +2544,17 @@ __metadata: languageName: node linkType: hard +"external-editor@npm:^3.0.3": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: ^0.7.0 + iconv-lite: ^0.4.24 + tmp: ^0.0.33 + checksum: 1c2a616a73f1b3435ce04030261bed0e22d4737e14b090bb48e58865da92529c9f2b05b893de650738d55e692d071819b45e1669259b2b354bc3154d27a698c7 + languageName: node + linkType: hard + "fast-glob@npm:^3.2.12": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" @@ -2350,6 +2584,15 @@ __metadata: languageName: node linkType: hard +"figures@npm:^3.0.0": + version: 3.2.0 + resolution: "figures@npm:3.2.0" + dependencies: + escape-string-regexp: ^1.0.5 + checksum: 85a6ad29e9aca80b49b817e7c89ecc4716ff14e3779d9835af554db91bac41c0f289c418923519392a1e582b4d10482ad282021330cd045bb7b80c84152f2a2b + languageName: node + linkType: hard + "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -2472,6 +2715,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + "get-func-name@npm:^2.0.0": version: 2.0.0 resolution: "get-func-name@npm:2.0.0" @@ -2572,6 +2822,13 @@ __metadata: languageName: node linkType: hard +"graphql@npm:^15.0.0 || ^16.0.0": + version: 16.6.0 + resolution: "graphql@npm:16.6.0" + checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e + languageName: node + linkType: hard + "has-bigints@npm:^1.0.1": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -2634,6 +2891,13 @@ __metadata: languageName: node linkType: hard +"headers-polyfill@npm:^3.1.0, headers-polyfill@npm:^3.1.2": + version: 3.1.2 + resolution: "headers-polyfill@npm:3.1.2" + checksum: 510ca9637ef652404dbd432e680418f8d418ba18094ef2f64c3d8de955ebf6e68d553c7f0aeaa5fc937d130b139c1e2d7c2066cd4cf0f740a4627924eaaee9db + languageName: node + linkType: hard + "history@npm:^5.3.0": version: 5.3.0 resolution: "history@npm:5.3.0" @@ -2707,6 +2971,22 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:^0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: ">= 2.1.2 < 3" + checksum: bd9f120f5a5b306f0bc0b9ae1edeb1577161503f5f8252a20f1a9e56ef8775c9959fd01c55f2d3a39d9a8abaf3e30c1abeb1895f367dcbbe0a8fd1c9ca01c4f6 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e + languageName: node + linkType: hard + "immer@npm:^9.0.16": version: 9.0.16 resolution: "immer@npm:9.0.16" @@ -2762,13 +3042,36 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3": +"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 languageName: node linkType: hard +"inquirer@npm:^8.2.0": + version: 8.2.5 + resolution: "inquirer@npm:8.2.5" + 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 + wrap-ansi: ^7.0.0 + checksum: f13ee4c444187786fb393609dedf6b30870115a57b603f2e6424f29a99abc13446fd45ee22461c33c9c40a92a60a8df62d0d6b25d74fc6676fa4cb211de55b55 + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.0 resolution: "ip@npm:2.0.0" @@ -2776,7 +3079,7 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.1.0, is-arguments@npm:^1.1.1": +"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.0, is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" dependencies: @@ -2867,6 +3170,15 @@ __metadata: languageName: node linkType: hard +"is-generator-function@npm:^1.0.7": + version: 1.0.10 + resolution: "is-generator-function@npm:1.0.10" + dependencies: + has-tostringtag: ^1.0.0 + checksum: d54644e7dbaccef15ceb1e5d91d680eb5068c9ee9f9eb0a9e04173eb5542c9b51b5ab52c5537f5703e48d5fddfd376817c1ca07a84a407b7115b769d4bdde72b + languageName: node + linkType: hard + "is-glob@npm:^4.0.1": version: 4.0.1 resolution: "is-glob@npm:4.0.1" @@ -2885,6 +3197,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -2899,6 +3218,13 @@ __metadata: languageName: node linkType: hard +"is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 930765cdc6d81ab8f1bbecbea4a8d35c7c6d88a3ff61f3630e0fc7f22d624d7661c1df05c58547d0eb6a639dfa9304682c8e342c4113a6ed51472b704cee2928 + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -2957,7 +3283,7 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.10": +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3": version: 1.1.10 resolution: "is-typed-array@npm:1.1.10" dependencies: @@ -2970,6 +3296,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52 + languageName: node + linkType: hard + "is-weakmap@npm:^2.0.1": version: 2.0.1 resolution: "is-weakmap@npm:2.0.1" @@ -3063,6 +3396,13 @@ __metadata: languageName: node linkType: hard +"js-levenshtein@npm:^1.1.6": + version: 1.1.6 + resolution: "js-levenshtein@npm:1.1.6" + checksum: 409f052a7f1141be4058d97da7860e08efd97fc588b7a4c5cfa0548bc04f6d576644dae65ab630266dff685d56fb90d494e03d4d79cb484c287746b4f1bf0694 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -3200,13 +3540,23 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15": +"lodash@npm:^4.17.15, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 languageName: node linkType: hard +"log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: ^4.1.0 + is-unicode-supported: ^0.1.0 + checksum: fce1497b3135a0198803f9f07464165e9eb83ed02ceb2273930a6f8a508951178d8cf4f0378e9d28300a2ed2bc49050995d2bd5f53ab716bb15ac84d58c6ef74 + languageName: node + linkType: hard + "loose-envify@npm:^1.1.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -3270,6 +3620,15 @@ __metadata: languageName: node linkType: hard +"lz-string@npm:^1.5.0": + version: 1.5.0 + resolution: "lz-string@npm:1.5.0" + bin: + lz-string: bin/bin.js + checksum: 1ee98b4580246fd90dd54da6e346fb1caefcf05f677c686d9af237a157fdea3fd7c83a4bc58f858cd5b10a34d27afe0fdcbd0505a47e0590726a873dc8b8f65d + languageName: node + linkType: hard + "magic-string@npm:^0.27.0": version: 0.27.0 resolution: "magic-string@npm:0.27.0" @@ -3346,6 +3705,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a + languageName: node + linkType: hard + "min-indent@npm:^1.0.0": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -3518,6 +3884,47 @@ __metadata: languageName: node linkType: hard +"msw@npm:^1.2.1": + version: 1.2.1 + resolution: "msw@npm:1.2.1" + dependencies: + "@mswjs/cookies": ^0.2.2 + "@mswjs/interceptors": ^0.17.5 + "@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: ^15.0.0 || ^16.0.0 + headers-polyfill: ^3.1.2 + inquirer: ^8.2.0 + is-node-process: ^1.2.0 + js-levenshtein: ^1.1.6 + node-fetch: ^2.6.7 + outvariant: ^1.4.0 + path-to-regexp: ^6.2.0 + strict-event-emitter: ^0.4.3 + type-fest: ^2.19.0 + yargs: ^17.3.1 + peerDependencies: + typescript: ">= 4.4.x <= 5.0.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 97b9c5ffc81b4380dd24ff40ac52ca02f406c88f86d8209ba991da19aad0bcfc37e807794aa1b9bf29f89cd86fd31e492bf4418c5ff02559b4f80d6ce4fb417b + languageName: node + linkType: hard + +"mute-stream@npm:0.0.8": + version: 0.0.8 + resolution: "mute-stream@npm:0.0.8" + checksum: ff48d251fc3f827e5b1206cda0ffdaec885e56057ee86a3155e1951bc940fd5f33531774b1cc8414d7668c10a8907f863f6561875ee6e8768931a62121a531a1 + languageName: node + linkType: hard + "nanoid@npm:^3.3.4": version: 3.3.4 resolution: "nanoid@npm:3.3.4" @@ -3534,6 +3941,20 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^2.6.7": + version: 2.6.9 + resolution: "node-fetch@npm:2.6.9" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: acb04f9ce7224965b2b59e71b33c639794d8991efd73855b0b250921382b38331ffc9d61bce502571f6cc6e11a8905ca9b1b6d4aeb586ab093e2756a1fd190d0 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -3657,6 +4078,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^5.1.0": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: ^2.1.0 + checksum: 2478859ef817fc5d4e9c2f9e5728512ddd1dbc9fb7829ad263765bb6d3b91ce699d6e2332eef6b7dff183c2f490bd3349f1666427eaba4469fba0ac38dfd0d34 + languageName: node + linkType: hard + "optionator@npm:^0.8.1": version: 0.8.3 resolution: "optionator@npm:0.8.3" @@ -3671,6 +4101,37 @@ __metadata: languageName: node linkType: hard +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + 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 + checksum: 28d476ee6c1049d68368c0dc922e7225e3b5600c3ede88fade8052837f9ed342625fdaa84a6209302587c8ddd9b664f71f0759833cbdb3a4cf81344057e63c63 + languageName: node + linkType: hard + +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + +"outvariant@npm:^1.2.1, outvariant@npm:^1.4.0": + version: 1.4.0 + resolution: "outvariant@npm:1.4.0" + checksum: ec32dfc315c464bb6e4906b2f450d259ce0b86caf70b70b249054359d9af21a7fccf53a8b6aa232f8d718449e31c1cfa594e6ebffaafe7bf908b502495256d7b + languageName: node + linkType: hard + "p-limit@npm:^4.0.0": version: 4.0.0 resolution: "p-limit@npm:4.0.0" @@ -3733,6 +4194,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^6.2.0": + version: 6.2.1 + resolution: "path-to-regexp@npm:6.2.1" + checksum: f0227af8284ea13300f4293ba111e3635142f976d4197f14d5ad1f124aebd9118783dd2e5f1fe16f7273743cc3dbeddfb7493f237bb27c10fdae07020cc9b698 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -4118,7 +4586,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.6.0": +"readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -4236,6 +4704,13 @@ __metadata: languageName: node linkType: hard +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80 + languageName: node + linkType: hard + "requires-port@npm:^1.0.0": version: 1.0.0 resolution: "requires-port@npm:1.0.0" @@ -4290,6 +4765,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: ^5.1.0 + signal-exit: ^3.0.2 + checksum: f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -4368,6 +4853,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^2.4.0": + version: 2.4.1 + resolution: "run-async@npm:2.4.1" + checksum: a2c88aa15df176f091a2878eb840e68d0bdee319d8d97bbb89112223259cebecb94bc0defd735662b83c2f7a30bed8cddb7d1674eb48ae7322dc602b22d03797 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -4377,6 +4869,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.5.5": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: ^2.1.0 + checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119 + languageName: node + linkType: hard + "safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -4391,7 +4892,7 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3.0.0": +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 @@ -4467,6 +4968,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.4.6": + version: 2.6.0 + resolution: "set-cookie-parser@npm:2.6.0" + checksum: bf11ebc594c53d84588f1b4c04f1b8ce14e0498b1c011b3d76b5c6d5aac481bbc3f7c5260ec4ce99bdc1d9aed19f9fc315e73166a36ca74d0f12349a73f6bdc9 + languageName: node + linkType: hard + "side-channel@npm:^1.0.4": version: 1.0.4 resolution: "side-channel@npm:1.0.4" @@ -4485,7 +4993,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -4611,7 +5119,23 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.2.3": +"strict-event-emitter@npm:^0.2.4": + version: 0.2.8 + resolution: "strict-event-emitter@npm:0.2.8" + dependencies: + events: ^3.3.0 + checksum: 6ac06fe72a6ee6ae64d20f1dd42838ea67342f1b5f32b03b3050d73ee6ecee44b4d5c4ed2965a7154b47991e215f373d4e789e2b2be2769cd80e356126c2ca53 + languageName: node + linkType: hard + +"strict-event-emitter@npm:^0.4.3": + version: 0.4.6 + resolution: "strict-event-emitter@npm:0.4.6" + checksum: 4f4f2909613e7811de789991c06bfb770d6d6987e2ec5c66fa7485d0f07cc4e7e32eba0dcf26cee6d86af6c92946d7f4acdfaff57d0c4114df2cfa1bf0e3c091 + languageName: node + linkType: hard + +"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -4642,7 +5166,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.1": +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -4760,6 +5284,13 @@ __metadata: languageName: node linkType: hard +"through@npm:^2.3.6": + version: 2.3.8 + resolution: "through@npm:2.3.8" + checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd + languageName: node + linkType: hard + "tinybench@npm:^2.3.1": version: 2.3.1 resolution: "tinybench@npm:2.3.1" @@ -4781,6 +5312,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: ~1.0.2 + checksum: 902d7aceb74453ea02abbf58c203f4a8fc1cead89b60b31e354f74ed5b3fb09ea817f94fb310f884a5d16987dd9fa5a735412a7c2dd088dd3d415aa819ae3a28 + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -4825,6 +5365,13 @@ __metadata: languageName: node linkType: hard +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3 + languageName: node + linkType: hard + "tsconfck@npm:^2.0.1": version: 2.0.2 resolution: "tsconfck@npm:2.0.2" @@ -4846,6 +5393,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.1.0": + version: 2.5.0 + resolution: "tslib@npm:2.5.0" + checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + languageName: node + linkType: hard + "type-check@npm:~0.3.2": version: 0.3.2 resolution: "type-check@npm:0.3.2" @@ -4862,6 +5416,20 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: e6b32a3b3877f04339bae01c193b273c62ba7bfc9e325b8703c4ee1b32dc8fe4ef5dfa54bf78265e069f7667d058e360ae0f37be5af9f153b22382cd55a9afe0 + languageName: node + linkType: hard + +"type-fest@npm:^2.19.0": + version: 2.19.0 + resolution: "type-fest@npm:2.19.0" + checksum: a4ef07ece297c9fba78fc1bd6d85dff4472fe043ede98bd4710d2615d15776902b595abf62bd78339ed6278f021235fb28a96361f8be86ed754f778973a0d278 + languageName: node + linkType: hard + "typed-redux-saga@npm:^1.5.0": version: 1.5.0 resolution: "typed-redux-saga@npm:1.5.0" @@ -4996,6 +5564,19 @@ __metadata: languageName: node linkType: hard +"util@npm:^0.12.3": + version: 0.12.5 + resolution: "util@npm:0.12.5" + dependencies: + inherits: ^2.0.3 + is-arguments: ^1.0.4 + is-generator-function: ^1.0.7 + is-typed-array: ^1.1.3 + which-typed-array: ^1.1.2 + checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a + languageName: node + linkType: hard + "vite-node@npm:0.28.2": version: 0.28.2 resolution: "vite-node@npm:0.28.2" @@ -5123,6 +5704,35 @@ __metadata: languageName: node linkType: hard +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 814e9d1ddcc9798f7377ffa448a5a3892232b9275ebb30a41b529607691c0491de47cba426e917a4d08ded3ee7e9ba2f3fe32e62ee3cd9c7d3bafb7754bd553c + languageName: node + linkType: hard + +"web-encoding@npm:^1.1.5": + version: 1.1.5 + resolution: "web-encoding@npm:1.1.5" + dependencies: + "@zxing/text-encoding": 0.9.0 + util: ^0.12.3 + dependenciesMeta: + "@zxing/text-encoding": + optional: true + checksum: 2234a2b122f41006ce07859b3c0bf2e18f46144fda2907d5db0b571b76aa5c26977c646100ad9c00d2f8a4f6f2b848bc02147845d8c447ab365ec4eff376338d + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c + languageName: node + linkType: hard + "webidl-conversions@npm:^7.0.0": version: 7.0.0 resolution: "webidl-conversions@npm:7.0.0" @@ -5156,6 +5766,16 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: ~0.0.3 + webidl-conversions: ^3.0.0 + checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2" @@ -5181,7 +5801,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.8": +"which-typed-array@npm:^1.1.2, which-typed-array@npm:^1.1.8": version: 1.1.9 resolution: "which-typed-array@npm:1.1.9" dependencies: @@ -5234,6 +5854,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -5277,6 +5908,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30 + languageName: node + linkType: hard + "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -5298,6 +5936,28 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c + languageName: node + linkType: hard + +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: ^8.0.1 + 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.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a + languageName: node + linkType: hard + "yocto-queue@npm:^1.0.0": version: 1.0.0 resolution: "yocto-queue@npm:1.0.0"