diff --git a/.github/workflows/package-js-tests.yml b/.github/workflows/package-js-tests.yml index 99d1edce1..7ca6e499b 100644 --- a/.github/workflows/package-js-tests.yml +++ b/.github/workflows/package-js-tests.yml @@ -43,3 +43,6 @@ jobs: sudo yarn global add yalc - name: Run JS unit tests for Renderer package run: yarn test + # TODO: Remove this once we made these tests compatible with React 19 + - name: Run JS unit tests for Renderer package with React 18 (for tests not compatible with React 19) + run: yarn test:react-18 diff --git a/Gemfile.lock b/Gemfile.lock index d962a0c18..85315a312 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -385,11 +385,6 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (~> 4.0, < 4.11) - webpacker (6.0.0.rc.6) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) webrick (1.8.1) websocket (1.2.10) websocket-driver (0.7.6) @@ -444,7 +439,6 @@ DEPENDENCIES turbolinks uglifier webdrivers (= 5.3.0) - webpacker (= 6.0.0.rc.6) BUNDLED WITH 2.5.9 diff --git a/jest.config.js b/jest.config.js index 09319b866..38eea025f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,15 @@ module.exports = { preset: 'ts-jest/presets/js-with-ts', testEnvironment: 'jsdom', setupFiles: ['/node_package/tests/jest.setup.js'], + // TODO: Remove this once we made RSCClientRoot compatible with React 19 + moduleNameMapper: process.env.USE_REACT_18 + ? { + '^react$': '/node_modules/react-18', + '^react/(.*)$': '/node_modules/react-18/$1', + '^react-dom$': '/node_modules/react-dom-18', + '^react-dom/(.*)$': '/node_modules/react-dom-18/$1', + } + : { + 'react-server-dom-webpack/client': '/node_package/tests/emptyForTesting.js', + }, }; diff --git a/knip.ts b/knip.ts index 11c0a21b0..aae1c27da 100644 --- a/knip.ts +++ b/knip.ts @@ -32,6 +32,10 @@ const config: KnipConfig = { 'eslint-plugin-jsx-a11y', 'eslint-plugin-react', 'react-server-dom-webpack', + 'cross-fetch', + 'jsdom', + 'react-18', + 'react-dom-18', ], }, 'spec/dummy': { diff --git a/lib/react_on_rails/configuration.rb b/lib/react_on_rails/configuration.rb index 0e90ac800..4ef59b476 100644 --- a/lib/react_on_rails/configuration.rb +++ b/lib/react_on_rails/configuration.rb @@ -9,6 +9,7 @@ def self.configure end DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze + DEFAULT_REACT_CLIENT_MANIFEST_FILE = "react-client-manifest.json" def self.configuration @configuration ||= Configuration.new( @@ -18,6 +19,7 @@ def self.configuration generated_assets_dir: "", server_bundle_js_file: "", rsc_bundle_js_file: "", + react_client_manifest_file: DEFAULT_REACT_CLIENT_MANIFEST_FILE, prerender: false, auto_load_bundle: false, replay_console: true, @@ -57,8 +59,8 @@ class Configuration :server_render_method, :random_dom_id, :auto_load_bundle, :same_bundle_for_client_and_server, :rendering_props_extension, :make_generated_server_bundle_the_entrypoint, - :defer_generated_component_packs, :rsc_bundle_js_file, - :force_load + :defer_generated_component_packs, :force_load, :rsc_bundle_js_file, + :react_client_manifest_file # rubocop:disable Metrics/AbcSize def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil, @@ -74,7 +76,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil, random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil, components_subdirectory: nil, auto_load_bundle: nil, force_load: nil, - rsc_bundle_js_file: nil) + rsc_bundle_js_file: nil, react_client_manifest_file: nil) self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root self.generated_assets_dirs = generated_assets_dirs self.generated_assets_dir = generated_assets_dir @@ -102,6 +104,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender # Server rendering: self.server_bundle_js_file = server_bundle_js_file self.rsc_bundle_js_file = rsc_bundle_js_file + self.react_client_manifest_file = react_client_manifest_file self.same_bundle_for_client_and_server = same_bundle_for_client_and_server self.server_renderer_pool_size = self.development_mode ? 1 : server_renderer_pool_size self.server_renderer_timeout = server_renderer_timeout # seconds @@ -247,6 +250,8 @@ def ensure_webpack_generated_files_exists files = ["manifest.json"] files << server_bundle_js_file if server_bundle_js_file.present? files << rsc_bundle_js_file if rsc_bundle_js_file.present? + files << react_client_manifest_file if react_client_manifest_file.present? + self.webpack_generated_files = files end diff --git a/lib/react_on_rails/helper.rb b/lib/react_on_rails/helper.rb index 23d32aa83..9f26c78c4 100644 --- a/lib/react_on_rails/helper.rb +++ b/lib/react_on_rails/helper.rb @@ -436,7 +436,7 @@ def internal_rsc_react_component(react_component_name, options = {}) render_options = create_render_options(react_component_name, options) json_stream = server_rendered_react_component(render_options) json_stream.transform do |chunk| - chunk[:html].html_safe + "#{chunk.to_json}\n".html_safe end end @@ -691,10 +691,7 @@ def server_rendered_react_component(render_options) # rubocop:disable Metrics/Cy js_code: js_code) end - # TODO: handle errors for rsc streams - return result if render_options.rsc? - - if render_options.stream? + if render_options.stream? || render_options.rsc? result.transform do |chunk_json_result| if should_raise_streaming_prerender_error?(chunk_json_result, render_options) raise_prerender_error(chunk_json_result, react_component_name, props, js_code) diff --git a/lib/react_on_rails/packer_utils.rb b/lib/react_on_rails/packer_utils.rb index 97999fe5e..df71afd4b 100644 --- a/lib/react_on_rails/packer_utils.rb +++ b/lib/react_on_rails/packer_utils.rb @@ -45,6 +45,10 @@ def self.dev_server_running? packer.dev_server.running? end + def self.dev_server_url + "#{packer.dev_server.protocol}://#{packer.dev_server.host_with_port}" + end + def self.shakapacker_version return @shakapacker_version if defined?(@shakapacker_version) return nil unless ReactOnRails::Utils.gem_available?("shakapacker") @@ -79,12 +83,27 @@ def self.bundle_js_uri_from_packer(bundle_name) if packer.dev_server.running? && (!is_bundle_running_on_server || ReactOnRails.configuration.same_bundle_for_client_and_server) - "#{packer.dev_server.protocol}://#{packer.dev_server.host_with_port}#{hashed_bundle_name}" + "#{dev_server_url}#{hashed_bundle_name}" else File.expand_path(File.join("public", hashed_bundle_name)).to_s end end + def self.public_output_uri_path + "#{packer.config.public_output_path.relative_path_from(packer.config.public_path)}/" + end + + # The function doesn't ensure that the asset exists. + # - It just returns url to the asset if dev server is running + # - Otherwise it returns file path to the asset + def self.asset_uri_from_packer(asset_name) + if dev_server_running? + "#{dev_server_url}/#{public_output_uri_path}#{asset_name}" + else + File.join(packer_public_output_path, asset_name).to_s + end + end + def self.precompile? return ::Webpacker.config.webpacker_precompile? if using_webpacker_const? return ::Shakapacker.config.shakapacker_precompile? if using_shakapacker_const? diff --git a/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb b/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb index ed130cd89..792fc85fd 100644 --- a/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +++ b/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb @@ -234,8 +234,6 @@ def file_url_to_string(url) end def parse_result_and_replay_console_messages(result_string, render_options) - return { html: result_string } if render_options.rsc? - result = nil begin result = JSON.parse(result_string) diff --git a/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb b/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb index e951df322..f3014af8d 100644 --- a/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +++ b/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb @@ -50,8 +50,8 @@ def stale_generated_files(files) def all_compiled_assets @all_compiled_assets ||= begin webpack_generated_files = @webpack_generated_files.map do |bundle_name| - if bundle_name == ReactOnRails.configuration.server_bundle_js_file - ReactOnRails::Utils.server_bundle_js_file_path + if bundle_name == ReactOnRails.configuration.react_client_manifest_file + ReactOnRails::Utils.react_client_manifest_file_path else ReactOnRails::Utils.bundle_js_file_path(bundle_name) end diff --git a/lib/react_on_rails/utils.rb b/lib/react_on_rails/utils.rb index 6835aff2a..c1a794a8f 100644 --- a/lib/react_on_rails/utils.rb +++ b/lib/react_on_rails/utils.rb @@ -109,6 +109,17 @@ def self.rsc_bundle_js_file_path @rsc_bundle_path = bundle_js_file_path(bundle_name) end + def self.react_client_manifest_file_path + return @react_client_manifest_path if @react_client_manifest_path && !Rails.env.development? + + file_name = ReactOnRails.configuration.react_client_manifest_file + @react_client_manifest_path = if ReactOnRails::PackerUtils.using_packer? + ReactOnRails::PackerUtils.asset_uri_from_packer(file_name) + else + File.join(generated_assets_full_path, file_name) + end + end + def self.running_on_windows? (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil end diff --git a/node_package/src/RSCClientRoot.ts b/node_package/src/RSCClientRoot.ts index 81b3f6a01..b827bc272 100644 --- a/node_package/src/RSCClientRoot.ts +++ b/node_package/src/RSCClientRoot.ts @@ -1,22 +1,38 @@ import * as React from 'react'; import RSDWClient from 'react-server-dom-webpack/client'; +import { fetch } from './utils'; +import transformRSCStreamAndReplayConsoleLogs from './transformRSCStreamAndReplayConsoleLogs'; -if (!('use' in React)) { +if (!('use' in React && typeof React.use === 'function')) { throw new Error('React.use is not defined. Please ensure you are using React 18 with experimental features enabled or React 19+ to use server components.'); } const { use } = React; -const renderCache: Record> = {}; +let renderCache: Record> = {}; +export const resetRenderCache = () => { + renderCache = {}; +} export type RSCClientRootProps = { componentName: string; rscRenderingUrlPath: string; } +const createFromFetch = async (fetchPromise: Promise) => { + const response = await fetchPromise; + const stream = response.body; + if (!stream) { + throw new Error('No stream found in response'); + } + const transformedStream = transformRSCStreamAndReplayConsoleLogs(stream); + return RSDWClient.createFromReadableStream(transformedStream); +} + const fetchRSC = ({ componentName, rscRenderingUrlPath }: RSCClientRootProps) => { if (!renderCache[componentName]) { - renderCache[componentName] = RSDWClient.createFromFetch(fetch(`${rscRenderingUrlPath}/${componentName}`)) as Promise; + const strippedUrlPath = rscRenderingUrlPath.replace(/^\/|\/$/g, ''); + renderCache[componentName] = createFromFetch(fetch(`/${strippedUrlPath}/${componentName}`)) as Promise; } return renderCache[componentName]; } diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts index fa0ae10ef..5c4bd24e5 100644 --- a/node_package/src/ReactOnRailsRSC.ts +++ b/node_package/src/ReactOnRailsRSC.ts @@ -1,13 +1,21 @@ -// @ts-expect-error will define this module types later -import { renderToReadableStream } from 'react-server-dom-webpack/server.edge'; -import { PassThrough } from 'stream'; -import fs from 'fs'; +import { renderToPipeableStream } from 'react-server-dom-webpack/server.node'; +import { PassThrough, Readable } from 'stream'; +import type { ReactElement } from 'react'; -import { RenderParams } from './types'; -import ComponentRegistry from './ComponentRegistry'; -import createReactOutput from './createReactOutput'; -import { isPromise, isServerRenderHash } from './isServerRenderResult'; +import { RSCRenderParams, StreamRenderState } from './types'; import ReactOnRails from './ReactOnRails'; +import buildConsoleReplay from './buildConsoleReplay'; +import handleError from './handleError'; +import { + convertToError, + createResultObject, +} from './serverRenderUtils'; + +import { + streamServerRenderedComponent, + transformRenderStreamChunksToResultObject, +} from './streamServerRenderedReactComponent'; +import loadReactClientManifest from './loadReactClientManifest'; const stringToStream = (str: string) => { const stream = new PassThrough(); @@ -16,68 +24,49 @@ const stringToStream = (str: string) => { return stream; }; -const getBundleConfig = () => { - const bundleConfig = JSON.parse(fs.readFileSync('./public/webpack/development/react-client-manifest.json', 'utf8')); - // remove file:// from keys - const newBundleConfig: { [key: string]: unknown } = {}; - for (const [key, value] of Object.entries(bundleConfig)) { - newBundleConfig[key.replace('file://', '')] = value; - } - return newBundleConfig; -} - -ReactOnRails.serverRenderRSCReactComponent = (options: RenderParams) => { - const { name, domNodeId, trace, props, railsContext, throwJsErrors } = options; - - let renderResult: null | PassThrough = null; +const streamRenderRSCComponent = (reactElement: ReactElement, options: RSCRenderParams): Readable => { + const { throwJsErrors, reactClientManifestFileName } = options; + const renderState: StreamRenderState = { + result: null, + hasErrors: false, + isShellReady: true + }; + const { pipeToTransform, readableStream, emitError } = transformRenderStreamChunksToResultObject(renderState); try { - const componentObj = ComponentRegistry.get(name); - if (componentObj.isRenderer) { - throw new Error(`\ -Detected a renderer while server rendering component '${name}'. \ -See https://github.com/shakacode/react_on_rails#renderer-functions`); - } - - const reactRenderingResult = createReactOutput({ - componentObj, - domNodeId, - trace, - props, - railsContext, - }); - - if (isServerRenderHash(reactRenderingResult) || isPromise(reactRenderingResult)) { - throw new Error('Server rendering of streams is not supported for server render hashes or promises.'); - } - - renderResult = new PassThrough(); - let finalValue = ""; - const streamReader = renderToReadableStream(reactRenderingResult, getBundleConfig()).getReader(); - const decoder = new TextDecoder(); - const processStream = async () => { - const { done, value } = await streamReader.read(); - if (done) { - renderResult?.push(null); - // @ts-expect-error value is not typed - debugConsole.log('value', finalValue); - return; + const rscStream = renderToPipeableStream( + reactElement, + loadReactClientManifest(reactClientManifestFileName), + { + onError: (err) => { + const error = convertToError(err); + console.error("Error in RSC stream", error); + if (throwJsErrors) { + emitError(error); + } + renderState.hasErrors = true; + renderState.error = error; + } } - - finalValue += decoder.decode(value); - renderResult?.push(value); - processStream(); - } - processStream(); - } catch (e: unknown) { - if (throwJsErrors) { - throw e; - } - - renderResult = stringToStream(`Error: ${e}`); + ); + pipeToTransform(rscStream); + return readableStream; + } catch (e) { + const error = convertToError(e); + renderState.hasErrors = true; + renderState.error = error; + const htmlResult = handleError({ e: error, name: options.name, serverSide: true }); + const jsonResult = JSON.stringify(createResultObject(htmlResult, buildConsoleReplay(), renderState)); + return stringToStream(jsonResult); } +}; - return renderResult; +ReactOnRails.serverRenderRSCReactComponent = (options: RSCRenderParams) => { + try { + return streamServerRenderedComponent(options, streamRenderRSCComponent); + } finally { + console.history = []; + } }; export * from './types'; diff --git a/node_package/src/loadReactClientManifest.ts b/node_package/src/loadReactClientManifest.ts new file mode 100644 index 000000000..e79181912 --- /dev/null +++ b/node_package/src/loadReactClientManifest.ts @@ -0,0 +1,17 @@ +import path from 'path'; +import fs from 'fs'; + +const loadedReactClientManifests = new Map(); + +export default function loadReactClientManifest(reactClientManifestFileName: string) { + // React client manifest is uploaded to node renderer as an asset. + // Renderer copies assets to the same place as the server-bundle.js and rsc-bundle.js. + // Thus, the __dirname of this code is where we can find the manifest file. + const manifestPath = path.resolve(__dirname, reactClientManifestFileName); + if (!loadedReactClientManifests.has(manifestPath)) { + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + loadedReactClientManifests.set(manifestPath, manifest); + } + + return loadedReactClientManifests.get(manifestPath)!; +} diff --git a/node_package/src/streamServerRenderedReactComponent.ts b/node_package/src/streamServerRenderedReactComponent.ts index 8e31b8ca2..7e76a5140 100644 --- a/node_package/src/streamServerRenderedReactComponent.ts +++ b/node_package/src/streamServerRenderedReactComponent.ts @@ -17,7 +17,7 @@ const stringToStream = (str: string): Readable => { return stream; }; -const transformRenderStreamChunksToResultObject = (renderState: StreamRenderState) => { +export const transformRenderStreamChunksToResultObject = (renderState: StreamRenderState) => { const consoleHistory = console.history; let previouslyReplayedConsoleMessages = 0; @@ -105,7 +105,15 @@ const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: return readableStream; } -const streamServerRenderedReactComponent = (options: RenderParams): Readable => { +type StreamRenderer = ( + reactElement: ReactElement, + options: P, +) => T; + +export const streamServerRenderedComponent = ( + options: P, + renderStrategy: StreamRenderer +): T => { const { name: componentName, domNodeId, trace, props, railsContext, throwJsErrors } = options; try { @@ -124,7 +132,7 @@ const streamServerRenderedReactComponent = (options: RenderParams): Readable => throw new Error('Server rendering of streams is not supported for server render hashes or promises.'); } - return streamRenderReactComponent(reactRenderingResult, options); + return renderStrategy(reactRenderingResult, options); } catch (e) { if (throwJsErrors) { throw e; @@ -133,8 +141,10 @@ const streamServerRenderedReactComponent = (options: RenderParams): Readable => const error = convertToError(e); const htmlResult = handleError({ e: error, name: componentName, serverSide: true }); const jsonResult = JSON.stringify(createResultObject(htmlResult, buildConsoleReplay(), { hasErrors: true, error, result: null })); - return stringToStream(jsonResult); + return stringToStream(jsonResult) as T; } }; +const streamServerRenderedReactComponent = (options: RenderParams): Readable => streamServerRenderedComponent(options, streamRenderReactComponent); + export default streamServerRenderedReactComponent; diff --git a/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts new file mode 100644 index 000000000..4fae6cf46 --- /dev/null +++ b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts @@ -0,0 +1,40 @@ +export default function transformRSCStreamAndReplayConsoleLogs(stream: ReadableStream) { + return new ReadableStream({ + async start(controller) { + const reader = stream.getReader(); + const decoder = new TextDecoder(); + const encoder = new TextEncoder(); + + let { value, done } = await reader.read(); + while (!done) { + const decodedValue = decoder.decode(value); + const jsonChunks = decodedValue.split('\n') + .filter(line => line.trim() !== '') + .map((line) => { + try { + return JSON.parse(line); + } catch (error) { + console.error('Error parsing JSON:', line, error); + throw error; + } + }); + + for (const jsonChunk of jsonChunks) { + const { html, consoleReplayScript } = jsonChunk; + controller.enqueue(encoder.encode(html)); + + const replayConsoleCode = consoleReplayScript?.trim().replace(/^/, '').replace(/<\/script>$/, ''); + if (replayConsoleCode?.trim() !== '') { + const scriptElement = document.createElement('script'); + scriptElement.textContent = replayConsoleCode; + document.body.appendChild(scriptElement); + } + } + + // eslint-disable-next-line no-await-in-loop + ({ value, done } = await reader.read()); + } + controller.close(); + } + }); +} diff --git a/node_package/src/types/index.ts b/node_package/src/types/index.ts index 48553e703..0817f9ccc 100644 --- a/node_package/src/types/index.ts +++ b/node_package/src/types/index.ts @@ -2,7 +2,7 @@ /// import type { ReactElement, ReactNode, Component, ComponentType } from 'react'; -import type { Readable, PassThrough } from 'stream'; +import type { Readable } from 'stream'; // Don't import redux just for the type definitions // See https://github.com/shakacode/react_on_rails/issues/1321 @@ -126,6 +126,10 @@ export interface RenderParams extends Params { renderingReturnsPromises: boolean; } +export interface RSCRenderParams extends RenderParams { + reactClientManifestFileName: string; +} + export interface CreateParams extends Params { componentObj: RegisteredComponent; shouldHydrate?: boolean; @@ -184,7 +188,7 @@ export interface ReactOnRails { getOrWaitForComponent(name: string): Promise; serverRenderReactComponent(options: RenderParams): null | string | Promise; streamServerRenderedReactComponent(options: RenderParams): Readable; - serverRenderRSCReactComponent(options: RenderParams): PassThrough; + serverRenderRSCReactComponent(options: RSCRenderParams): Readable; handleError(options: ErrorOptions): string | undefined; buildConsoleReplay(): string; registeredComponents(): Map; diff --git a/node_package/src/utils.ts b/node_package/src/utils.ts new file mode 100644 index 000000000..352905a17 --- /dev/null +++ b/node_package/src/utils.ts @@ -0,0 +1,8 @@ +// Override the fetch function to make it easier to test and for future use +const customFetch = (...args: Parameters) => { + const res = fetch(...args); + return res; +} + +// eslint-disable-next-line import/prefer-default-export +export { customFetch as fetch }; diff --git a/node_package/tests/RSCClientRoot.test.jsx b/node_package/tests/RSCClientRoot.test.jsx new file mode 100644 index 000000000..952cde6ff --- /dev/null +++ b/node_package/tests/RSCClientRoot.test.jsx @@ -0,0 +1,127 @@ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable import/first */ +/** + * @jest-environment jsdom + */ + +// Mock webpack require system for RSC +window.__webpack_require__ = jest.fn(); +window.__webpack_chunk_load__ = jest.fn(); + +import * as React from 'react'; +import { enableFetchMocks } from 'jest-fetch-mock'; +import { render, waitFor, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import path from 'path'; +import fs from 'fs'; +import { createNodeReadableStream } from './testUtils'; + +import RSCClientRoot, { resetRenderCache } from '../src/RSCClientRoot'; + +enableFetchMocks(); + +// TODO: Remove this once we made these tests compatible with React 19 +(process.env.USE_REACT_18 ? describe : describe.skip)('RSCClientRoot', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.resetModules(); + resetRenderCache(); + }); + + it('throws error when React.use is not defined', () => { + jest.mock('react', () => ({ + ...jest.requireActual('react'), + use: undefined, + })); + + expect(() => { + // Re-import to trigger the check + jest.requireActual('../src/RSCClientRoot'); + }).toThrow('React.use is not defined'); + }); + + const mockRSCRequest = (rscRenderingUrlPath = 'rsc-render') => { + const chunksDirectory = path.join( + __dirname, + 'fixtures', + 'rsc-payloads', + 'simple-shell-with-async-component', + ); + const chunk1 = JSON.parse(fs.readFileSync(path.join(chunksDirectory, 'chunk1.json'), 'utf8')); + const chunk2 = JSON.parse(fs.readFileSync(path.join(chunksDirectory, 'chunk2.json'), 'utf8')); + + const { stream, push } = createNodeReadableStream(); + window.fetchMock.mockResolvedValue(new Response(stream)); + + const props = { + componentName: 'TestComponent', + rscRenderingUrlPath, + }; + + const { rerender } = render(); + + return { + rerender: () => rerender(), + pushFirstChunk: () => push(JSON.stringify(chunk1)), + pushSecondChunk: () => push(JSON.stringify(chunk2)), + pushCustomChunk: (chunk) => push(chunk), + endStream: () => push(null), + }; + }; + + it('fetches and caches component data', async () => { + const { rerender, pushFirstChunk, pushSecondChunk, endStream } = mockRSCRequest(); + + expect(window.fetch).toHaveBeenCalledWith('/rsc-render/TestComponent'); + expect(window.fetch).toHaveBeenCalledTimes(1); + expect(screen.queryByText('StaticServerComponent')).not.toBeInTheDocument(); + + pushFirstChunk(); + await waitFor(() => expect(screen.getByText('StaticServerComponent')).toBeInTheDocument()); + expect(screen.getByText('Loading AsyncComponent...')).toBeInTheDocument(); + expect(screen.queryByText('AsyncComponent')).not.toBeInTheDocument(); + + pushSecondChunk(); + endStream(); + await waitFor(() => expect(screen.getByText('AsyncComponent')).toBeInTheDocument()); + expect(screen.queryByText('Loading AsyncComponent...')).not.toBeInTheDocument(); + + // Second render - should use cache + rerender(); + + expect(screen.getByText('AsyncComponent')).toBeInTheDocument(); + expect(window.fetch).toHaveBeenCalledTimes(1); + }); + + it('replays console logs', async () => { + const consoleSpy = jest.spyOn(console, 'log'); + const { rerender, pushFirstChunk, pushSecondChunk, endStream } = mockRSCRequest(); + + pushFirstChunk(); + await waitFor(() => expect(consoleSpy).toHaveBeenCalledWith('[SERVER] Console log at first chunk')); + expect(consoleSpy).toHaveBeenCalledTimes(1); + + pushSecondChunk(); + await waitFor(() => expect(consoleSpy).toHaveBeenCalledWith('[SERVER] Console log at second chunk')); + endStream(); + expect(consoleSpy).toHaveBeenCalledTimes(2); + + // On rerender, console logs should not be replayed again + rerender(); + expect(consoleSpy).toHaveBeenCalledTimes(2); + }); + + it('strips leading and trailing slashes from rscRenderingUrlPath', async () => { + const { pushFirstChunk, pushSecondChunk, endStream } = mockRSCRequest('/rsc-render/'); + + pushFirstChunk(); + pushSecondChunk(); + endStream(); + + await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('/rsc-render/TestComponent')); + expect(window.fetch).toHaveBeenCalledTimes(1); + + await waitFor(() => expect(screen.getByText('StaticServerComponent')).toBeInTheDocument()); + }); +}); diff --git a/node_package/tests/emptyForTesting.js b/node_package/tests/emptyForTesting.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/node_package/tests/emptyForTesting.js @@ -0,0 +1 @@ +export default {}; diff --git a/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk1.json b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk1.json new file mode 100644 index 000000000..5ea689406 --- /dev/null +++ b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk1.json @@ -0,0 +1,6 @@ +{ + "html": "1:\"$Sreact.suspense\"\n0:D{\"name\":\"StaticServerComponent\",\"env\":\"Server\"}\n2:D{\"name\":\"AsyncComponent\",\"env\":\"Server\"}\n0:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h1\",null,{\"children\":\"StaticServerComponent\"}],[\"$\",\"p\",null,{\"children\":\"This is a static server component\"}],[\"$\",\"$1\",null,{\"fallback\":[\"$\",\"div\",null,{\"children\":\"Loading AsyncComponent...\"}],\"children\":\"$L2\"}]]}]\n", + "consoleReplayScript": "\n\u003cscript id=\"consoleReplayLog\"\u003e\nconsole.log.apply(console, [\"[SERVER] Console log at first chunk\"]);\n\u003c/script\u003e", + "hasErrors": false, + "isShellReady": true +} diff --git a/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk2.json b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk2.json new file mode 100644 index 000000000..1737a3859 --- /dev/null +++ b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk2.json @@ -0,0 +1,6 @@ +{ + "html": "2:[\"$\",\"div\",null,{\"children\":\"AsyncComponent\"}]\n", + "consoleReplayScript": "\n\u003cscript id=\"consoleReplayLog\"\u003e\nconsole.log.apply(console, [\"[SERVER] Console log at second chunk\"]);\n\u003c/script\u003e", + "hasErrors": false, + "isShellReady": true +} diff --git a/node_package/tests/jest.setup.js b/node_package/tests/jest.setup.js index 24ae0b976..5b4acefa3 100644 --- a/node_package/tests/jest.setup.js +++ b/node_package/tests/jest.setup.js @@ -13,6 +13,33 @@ if (typeof window !== 'undefined' && typeof window.MessageChannel !== 'undefined if (typeof window !== 'undefined') { // eslint-disable-next-line global-require const { TextEncoder, TextDecoder } = require('util'); + // eslint-disable-next-line global-require + const { Readable } = require('stream'); + // eslint-disable-next-line global-require + const { ReadableStream, ReadableStreamDefaultReader } = require('stream/web'); + + // Mock the fetch function to return a ReadableStream instead of Node's Readable stream + // This matches browser behavior where fetch responses have ReadableStream bodies + // Node's fetch and polyfills like jest-fetch-mock return Node's Readable stream, + // so we convert it to a web-standard ReadableStream for consistency + // Note: Node's Readable stream exists in node 'stream' built-in module, can be imported as `import { Readable } from 'stream'` + jest.mock('../src/utils', () => ({ + ...jest.requireActual('../src/utils'), + fetch: (...args) => + jest + .requireActual('../src/utils') + .fetch(...args) + .then((res) => { + const originalBody = res.body; + if (originalBody instanceof Readable) { + Object.defineProperty(res, 'body', { + value: Readable.toWeb(originalBody), + }); + } + return res; + }), + })); + global.TextEncoder = TextEncoder; global.TextDecoder = TextDecoder; @@ -32,4 +59,6 @@ if (typeof window !== 'undefined') { }, }; }); + global.ReadableStream = ReadableStream; + global.ReadableStreamDefaultReader = ReadableStreamDefaultReader; } diff --git a/node_package/tests/testUtils.js b/node_package/tests/testUtils.js new file mode 100644 index 000000000..77e7dd577 --- /dev/null +++ b/node_package/tests/testUtils.js @@ -0,0 +1,33 @@ +import { Readable } from 'stream'; + +/** + * Creates a Node.js Readable stream with external push capability. + * Pusing a null or undefined chunk will end the stream. + * @returns {{ + * stream: Readable, + * push: (chunk: any) => void + * }} Object containing the stream and push function + */ +// eslint-disable-next-line import/prefer-default-export +export const createNodeReadableStream = () => { + const pendingChunks = []; + let pushFn; + const stream = new Readable({ + read() { + pushFn = this.push.bind(this); + if (pendingChunks.length > 0) { + pushFn(pendingChunks.shift()); + } + }, + }); + + const push = (chunk) => { + if (pushFn) { + pushFn(chunk); + } else { + pendingChunks.push(chunk); + } + }; + + return { stream, push }; +}; diff --git a/node_package/tests/utils.test.js b/node_package/tests/utils.test.js new file mode 100644 index 000000000..ab5e6eb30 --- /dev/null +++ b/node_package/tests/utils.test.js @@ -0,0 +1,44 @@ +import { enableFetchMocks } from 'jest-fetch-mock'; + +import { fetch } from '../src/utils'; +import { createNodeReadableStream } from './testUtils'; + +enableFetchMocks(); + +describe('fetch', () => { + it('streams body as ReadableStream', async () => { + // create Readable stream that emits 5 chunks with 10ms delay between each chunk + const { stream, push } = createNodeReadableStream(); + let n = 0; + const intervalId = setInterval(() => { + n += 1; + push(`chunk${n}`); + if (n === 5) { + clearInterval(intervalId); + push(null); + } + }, 10); + + global.fetchMock.mockResolvedValue(new Response(stream)); + + await fetch('/test').then(async (response) => { + console.log(response.body); + const { body } = response; + expect(body).toBeInstanceOf(ReadableStream); + + const reader = body.getReader(); + const chunks = []; + const decoder = new TextDecoder(); + let { done, value } = await reader.read(); + while (!done) { + chunks.push(decoder.decode(value)); + // eslint-disable-next-line no-await-in-loop + ({ done, value } = await reader.read()); + } + expect(chunks).toEqual(['chunk1', 'chunk2', 'chunk3', 'chunk4', 'chunk5']); + + // expect global.fetch to be called one time + expect(global.fetch).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/node_package/types/react-server-dom-webpack.d.ts b/node_package/types/react-server-dom-webpack.d.ts index ef81cc126..6f422e9c6 100644 --- a/node_package/types/react-server-dom-webpack.d.ts +++ b/node_package/types/react-server-dom-webpack.d.ts @@ -15,6 +15,29 @@ declare module 'react-server-dom-webpack/node-loader' { ): Promise; } +declare module 'react-server-dom-webpack/server.node' { + export interface Options { + environmentName?: string; + onError?: (error: unknown) => void; + onPostpone?: (reason: string) => void; + identifierPrefix?: string; + } + + export interface PipeableStream { + abort(reason: unknown): void; + pipe(destination: Writable): Writable; + } + + // Note: ReactClientValue is likely what React uses internally for RSC + // We're using 'unknown' here as it's the most accurate type we can use + // without accessing React's internal types + export function renderToPipeableStream( + model: unknown, + webpackMap: { [key: string]: unknown }, + options?: Options + ): PipeableStream; +} + declare module 'react-server-dom-webpack/client' { export const createFromFetch: (promise: Promise) => Promise; diff --git a/package.json b/package.json index db7f1f79c..dc2b11156 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "devDependencies": { "@babel/core": "^7.20.12", "@babel/preset-env": "^7.20.2", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^20.17.16", "@types/react": "^18.3.18", @@ -26,6 +29,7 @@ "@typescript-eslint/parser": "^6.18.1", "concurrently": "^8.2.2", "create-react-class": "^15.7.0", + "cross-fetch": "^4.1.0", "eslint": "^7.32.0", "eslint-config-prettier": "^7.0.0", "eslint-config-shakacode": "^16.0.1", @@ -35,19 +39,22 @@ "eslint-plugin-react": "^7.33.2", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-fetch-mock": "^3.0.3", + "jsdom": "^22.1.0", "knip": "^5.43.1", "nps": "^5.9.3", "prettier": "^2.8.8", "prop-types": "^15.8.1", "react": "^19.0.0", + "react-18": "npm:react@18.3.0-canary-670811593-20240322", "react-dom": "^19.0.0", - "react-server-dom-webpack": "^19.0.0", + "react-dom-18": "npm:react-dom@18.3.0-canary-670811593-20240322", + "react-server-dom-webpack": "18.3.0-canary-670811593-20240322", "redux": "^4.2.1", "ts-jest": "^29.2.5", "typescript": "^5.6.2" }, - "dependencies": { - }, + "dependencies": {}, "peerDependencies": { "react": ">= 16", "react-dom": ">= 16", @@ -58,6 +65,7 @@ ], "scripts": { "test": "jest node_package/tests", + "test:react-18": "USE_REACT_18=true jest node_package/tests/RSCClientRoot.test.jsx", "clean": "rm -rf node_package/lib", "start": "nps", "prepack": "nps build.prepack", diff --git a/spec/react_on_rails/packer_utils_spec.rb b/spec/react_on_rails/packer_utils_spec.rb index b7bd4f2e5..dc773b2a5 100644 --- a/spec/react_on_rails/packer_utils_spec.rb +++ b/spec/react_on_rails/packer_utils_spec.rb @@ -35,5 +35,46 @@ module ReactOnRails expect(described_class.shakapacker_version_requirement_met?(minimum_version)).to be(true) end end + + describe ".asset_uri_from_packer" do + let(:asset_name) { "test-asset.js" } + let(:public_output_path) { "/path/to/public/webpack/dev" } + + context "when dev server is running" do + before do + allow(described_class.packer).to receive(:dev_server).and_return( + instance_double( + ReactOnRails::PackerUtils.packer::DevServer, + running?: true, + protocol: "http", + host_with_port: "localhost:3035" + ) + ) + + allow(described_class.packer).to receive_message_chain("config.public_output_path") + .and_return(Pathname.new(public_output_path)) + allow(described_class.packer).to receive_message_chain("config.public_path") + .and_return(Pathname.new("/path/to/public")) + end + + it "returns asset URL with dev server path" do + expected_url = "http://localhost:3035/webpack/dev/test-asset.js" + expect(described_class.asset_uri_from_packer(asset_name)).to eq(expected_url) + end + end + + context "when dev server is not running" do + before do + allow(described_class.packer).to receive_message_chain("dev_server.running?").and_return(false) + allow(described_class.packer).to receive_message_chain("config.public_output_path") + .and_return(Pathname.new(public_output_path)) + end + + it "returns file path to the asset" do + expected_path = File.join(public_output_path, asset_name) + expect(described_class.asset_uri_from_packer(asset_name)).to eq(expected_path) + end + end + end end end diff --git a/spec/react_on_rails/utils_spec.rb b/spec/react_on_rails/utils_spec.rb index d87873ef0..eecc7d009 100644 --- a/spec/react_on_rails/utils_spec.rb +++ b/spec/react_on_rails/utils_spec.rb @@ -493,6 +493,73 @@ def mock_dev_server_running end end end + + describe ".react_client_manifest_file_path" do + before do + described_class.instance_variable_set(:@react_client_manifest_path, nil) + allow(ReactOnRails.configuration).to receive(:react_client_manifest_file) + .and_return("react-client-manifest.json") + end + + after do + described_class.instance_variable_set(:@react_client_manifest_path, nil) + end + + context "when using packer" do + let(:public_output_path) { "/path/to/public/webpack/dev" } + + before do + allow(ReactOnRails::PackerUtils).to receive(:using_packer?).and_return(true) + allow(ReactOnRails::PackerUtils.packer).to receive_message_chain("config.public_output_path") + .and_return(Pathname.new(public_output_path)) + allow(ReactOnRails::PackerUtils.packer).to receive_message_chain("config.public_path") + .and_return(Pathname.new("/path/to/public")) + end + + context "when dev server is running" do + before do + allow(ReactOnRails::PackerUtils.packer).to receive(:dev_server).and_return( + instance_double( + Object.const_get(ReactOnRails::PackerUtils.packer_type.capitalize)::DevServer, + running?: true, + protocol: "http", + host_with_port: "localhost:3035" + ) + ) + end + + it "returns manifest URL with dev server path" do + expected_url = "http://localhost:3035/webpack/dev/react-client-manifest.json" + expect(described_class.react_client_manifest_file_path).to eq(expected_url) + end + end + + context "when dev server is not running" do + before do + allow(ReactOnRails::PackerUtils.packer).to receive_message_chain("dev_server.running?") + .and_return(false) + end + + it "returns file path to the manifest" do + expected_path = File.join(public_output_path, "react-client-manifest.json") + expect(described_class.react_client_manifest_file_path).to eq(expected_path) + end + end + end + + context "when not using packer" do + before do + allow(ReactOnRails::PackerUtils).to receive(:using_packer?).and_return(false) + allow(described_class).to receive(:generated_assets_full_path) + .and_return("/path/to/generated/assets") + end + + it "returns joined path with generated_assets_full_path" do + expect(described_class.react_client_manifest_file_path) + .to eq("/path/to/generated/assets/react-client-manifest.json") + end + end + end end end # rubocop:enable Metrics/ModuleLength, Metrics/BlockLength diff --git a/yarn.lock b/yarn.lock index 8e96e32b4..259ea9c78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.4.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.1.tgz#2447a230bfe072c1659e6815129c03cf170710e3" + integrity sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ== + "@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -1077,6 +1082,13 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== +"@babel/runtime@^7.12.5": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.21.0": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" @@ -1588,11 +1600,50 @@ ignore "^5.1.8" p-map "^4.0.0" +"@testing-library/dom@^10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" + integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.3.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^6.6.3": + version "6.6.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" + integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + +"@testing-library/react@^16.2.0": + version "16.2.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.2.0.tgz#c96126ee01a49cdb47175721911b4a9432afc601" + integrity sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + "@types/babel__core@^7.1.14": version "7.20.0" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" @@ -1968,13 +2019,18 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^5.3.0: +aria-query@5.3.0, aria-query@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: dequal "^2.0.3" +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + array-buffer-byte-length@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" @@ -2355,6 +2411,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -2363,7 +2427,7 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.2, chalk@^4.1.2: +chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2530,6 +2594,20 @@ create-react-class@^15.7.0: loose-envify "^1.3.1" object-assign "^4.1.1" +cross-fetch@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.2.0.tgz#34e9192f53bc757d6614304d9e5e6fb4edb782e3" + integrity sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q== + dependencies: + node-fetch "^2.7.0" + +cross-fetch@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.1.0.tgz#8f69355007ee182e47fa692ecbaa37a52e43c3d2" + integrity sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw== + dependencies: + node-fetch "^2.7.0" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2539,6 +2617,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssom@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" @@ -2556,6 +2639,13 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +cssstyle@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" + integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== + dependencies: + rrweb-cssom "^0.6.0" + csstype@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" @@ -2575,6 +2665,15 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" +data-urls@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" + integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.0" + date-fns@^2.30.0: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" @@ -2613,6 +2712,11 @@ decimal.js@^10.4.2: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +decimal.js@^10.4.3: + version "10.5.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22" + integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== + dedent@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" @@ -2714,6 +2818,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + domexception@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" @@ -2782,7 +2896,7 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" -entities@^4.4.0: +entities@^4.4.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -4252,6 +4366,14 @@ jest-environment-node@^29.7.0: jest-mock "^29.7.0" jest-util "^29.7.0" +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" @@ -4547,6 +4669,35 @@ jsdom@^20.0.0: ws "^8.11.0" xml-name-validator "^4.0.0" +jsdom@^22.1.0: + version "22.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" + integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== + dependencies: + abab "^2.0.6" + cssstyle "^3.0.0" + data-urls "^4.0.0" + decimal.js "^10.4.3" + domexception "^4.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.4" + parse5 "^7.1.2" + rrweb-cssom "^0.6.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.1" + ws "^8.13.0" + xml-name-validator "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -4741,6 +4892,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + make-dir@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" @@ -4813,6 +4969,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimatch@9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" @@ -4864,6 +5025,13 @@ neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +node-fetch@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -4909,6 +5077,11 @@ nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== +nwsapi@^2.2.4: + version "2.2.16" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.16.tgz#177760bba02c351df1d2644e220c31dfec8cdb43" + integrity sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5152,6 +5325,13 @@ parse5@^7.0.0, parse5@^7.1.1: dependencies: entities "^4.4.0" +parse5@^7.1.2: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -5251,6 +5431,15 @@ prettier@^2.8.8: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -5272,6 +5461,11 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-polyfill@^8.1.3: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63" + integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== + prompts@^2.0.1: version "2.3.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" @@ -5306,6 +5500,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pure-rand@^6.0.0: version "6.0.4" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" @@ -5321,6 +5520,18 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +"react-18@npm:react@18.3.0-canary-670811593-20240322": + version "18.3.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.0-canary-670811593-20240322.tgz#3735250b45468d313ed36121324452bb5a732e9b" + integrity sha512-EI6+q3tOT+0z4OkB2sz842Ra/n/yz7b3jOJhSK1HQwi4Ng29VJzLGngWmSuxQ94YfdE3EBhpUKDfgNgzoKM9Vg== + +"react-dom-18@npm:react-dom@18.3.0-canary-670811593-20240322": + version "18.3.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.0-canary-670811593-20240322.tgz#ac677b164fd83050272bf985e740ed4ca65337be" + integrity sha512-AHxCnyDzZueXIHY4WA2Uba1yaL7/vbjhO3D3TWPQeruKD5MwgD0/xExZi0T104gBr6Thv6MEsLSxFjBAHhHKKg== + dependencies: + scheduler "0.24.0-canary-670811593-20240322" + react-dom@^19.0.0: version "19.0.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57" @@ -5333,19 +5544,23 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-is@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-server-dom-webpack@^19.0.0: - version "19.0.0" - resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-19.0.0.tgz#c60819b6cb54e317e675ddc0c5959ff915b789d0" - integrity sha512-hLug9KEXLc8vnU9lDNe2b2rKKDaqrp5gNiES4uyu2Up3FZfZJZmdwLFXlWzdA9gTB/6/cWduSB2K1Lfag2pSvw== +react-server-dom-webpack@18.3.0-canary-670811593-20240322: + version "18.3.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-18.3.0-canary-670811593-20240322.tgz#e9b99b1f0179357e5acbf2fbacaee88dd1e8bf3b" + integrity sha512-YaCk3AvvOXcOo0FL7SlAY2GVBeuZKFQ/5FfAtE48IjpI6MvXTwMBu3QVnT/Ukk9Y4M9GzpIbLtuc8hPjfFAOaw== dependencies: acorn-loose "^8.3.0" neo-async "^2.6.1" - webpack-sources "^3.2.0" react@^19.0.0: version "19.0.0" @@ -5357,6 +5572,14 @@ readline-sync@^1.4.7: resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + redux@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" @@ -5527,6 +5750,11 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rrweb-cssom@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" + integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== + run-parallel@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" @@ -5582,6 +5810,11 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +scheduler@0.24.0-canary-670811593-20240322: + version "0.24.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.24.0-canary-670811593-20240322.tgz#45c5c45f18a127ab4e3c805dd466bc231b20adf3" + integrity sha512-IGX6Fq969h1L0X7jV0sJ/EdI4fr+mRetbBNJl55nn+/RsCuQSVwgKnZG6Q3NByixDNbkRI8nRmWuhOm8NQowGQ== + scheduler@^0.25.0: version "0.25.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015" @@ -5898,6 +6131,13 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.1.tgz#0d8b7d01b23848ed7dbdf4baaaa31a8250d8cfa0" @@ -6013,6 +6253,18 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +tr46@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" + integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== + dependencies: + punycode "^2.3.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -6237,16 +6489,16 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -webpack-sources@^3.2.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" @@ -6267,6 +6519,22 @@ whatwg-url@^11.0.0: tr46 "^3.0.0" webidl-conversions "^7.0.0" +whatwg-url@^12.0.0, whatwg-url@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" + integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== + dependencies: + tr46 "^4.1.1" + webidl-conversions "^7.0.0" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -6382,6 +6650,11 @@ ws@^8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== +ws@^8.13.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xml-name-validator@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"