diff --git a/.github/workflows/publish-commit.yml b/.github/workflows/publish-commit.yml index 34bbe8d..8dc6c4d 100644 --- a/.github/workflows/publish-commit.yml +++ b/.github/workflows/publish-commit.yml @@ -97,6 +97,10 @@ jobs: if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server/') run: pnpm dlx pkg-pr-new@0.0 publish --compact --pnpm ./packages/react-server --comment=update + - name: Publish @lazarv/react-server-adapter-core + if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-core/') + run: pnpm dlx pkg-pr-new@0.0 publish --compact --pnpm ./packages/react-server-adapter-core --comment=update + - name: Publish @lazarv/react-server-adapter-vercel if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-vercel/') run: pnpm dlx pkg-pr-new@0.0 publish --compact --pnpm ./packages/react-server-adapter-vercel --comment=update diff --git a/.github/workflows/release-experimental.yml b/.github/workflows/release-experimental.yml index 7dc436f..b903a6a 100644 --- a/.github/workflows/release-experimental.yml +++ b/.github/workflows/release-experimental.yml @@ -66,6 +66,7 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_ENV - name: Prepare @lazarv/react-server + id: prepare-react-server if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server') working-directory: ./packages/react-server run: | @@ -75,7 +76,7 @@ jobs: - name: Publish @lazarv/react-server id: publish-react-server - if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server') + if: steps.prepare-react-server.outcome == 'success' working-directory: ./packages/react-server env: NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} @@ -88,14 +89,29 @@ jobs: run: | gh release create "v${{ env.VERSION }}" --generate-notes + - name: Prepare @lazarv/react-server-adapter-core + id: prepare-react-server-adapter-core + if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-core') + working-directory: ./packages/react-server-adapter-core + run: | + jq --arg new_version "${{ env.VERSION }}" '.version = $new_version' package.json > tmp.json && mv tmp.json package.json + + - name: Publish @lazarv/react-server-adapter-core + if: steps.prepare-react-server-adapter-core.outcome == 'success' + working-directory: ./packages/react-server-adapter-core + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} + run: pnpm publish --access=public --no-git-checks + - name: Prepare @lazarv/react-server-adapter-vercel + id: prepare-react-server-adapter-vercel if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-vercel') working-directory: ./packages/react-server-adapter-vercel run: | jq --arg new_version "${{ env.VERSION }}" '.version = $new_version' package.json > tmp.json && mv tmp.json package.json - name: Publish @lazarv/react-server-adapter-vercel - if: contains(needs.changed.outputs.all_changed_files, 'packages/react-server-adapter-vercel') + if: steps.prepare-react-server-adapter-vercel.outcome == 'success' working-directory: ./packages/react-server-adapter-vercel env: NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} diff --git a/docs/src/pages/en/(pages)/deploy/api.mdx b/docs/src/pages/en/(pages)/deploy/api.mdx new file mode 100644 index 0000000..f4dbf78 --- /dev/null +++ b/docs/src/pages/en/(pages)/deploy/api.mdx @@ -0,0 +1,145 @@ +--- +title: Adapter API +category: Deploy +order: 100 +--- + +import Link from "../../../../components/Link" + +# Adapter API + +By using the Adapter API available in the `@lazarv/react-server-adapter-core` package, you can easily create a deployment adapter for any deployment target. + +## Define the adapter handler + +The adapter handler is a function that you need to implement to handle the deployment of your application. It will be called by the build process and it will receive the information needed to prepare the application for deployment. + +An example of an adapter handler implementation can be found in the [Vercel adapter implementation](https://github.com/lazarv/react-server/blob/main/packages/react-server-adapter-vercel/index.mjs). + +You can start by copying the Vercel adapter implementation and then modify it to fit your needs. + +You need to export the adapter handler function from the file and then use the `createAdapter` function to create the adapter instance. + +You also need to export default a function that will be used to create the adapter instance when adapter options are provided by the user in the `react-server.config.js` file. + +```js +import { createAdapter } from "@lazarv/react-server-adapter-core"; + +export const adapter = createAdapter({ + name: "Vercel", + outDir: ".vercel", + outStaticDir: "static", + handler: async ({ adapterOptions, files, copy, config, reactServerDir, reactServerOutDir, root, options }) => { + // Your adapter handler implementation + }, + deploy: { + command: "vercel", + args: ["deploy", "--prebuilt"], + }, +}); + +export default function defineConfig(adapterOptions) { + return async (_, root, options) => adapter(adapterOptions, root, options); +} +``` + +You need to pass adapter properties to the `createAdapter` function to configure the adapter. These properties are: + +`name`: The name of the adapter. + +`outDir`: The directory where the adapter will output the deployment configuration. + +`outStaticDir`: The directory where the static files will be output. This is optional. When provided, the adapter will copy the static files to the output directory. + +`handler`: The adapter handler function. + +`deploy`: The deployment command and arguments. This is optional. When provided, the adapter will show what command the developer needs to run to deploy the application after it has been built. If the `--deploy` flag is provided during the build, the adapter will run this command. + +## Adapter handler + +The adapter handler function will receive the following properties: + +- [ ] `adapterOptions`: The adapter options passed from the `react-server.config.js` file. +- [ ] `files`: The files object contains the static files, assets, client files, public files, server files and the dependencies. +- [ ] `copy`: The copy object contains the functions to copy the files to the output directory. +- [ ] `config`: The configuration object contains the configuration of the application. +- [ ] `reactServerDir`: The path to the directory where the build output is located. +- [ ] `reactServerOutDir`: The directory name where the build output is located. +- [ ] `root`: The entry point of the application. +- [ ] `options`: The options object contains the options passed from the CLI. + +The `files` object contains the following functions: + +- [ ] `static`: The function to get the static files. +- [ ] `assets`: The function to get the assets files. +- [ ] `client`: The function to get the client files. +- [ ] `public`: The function to get the public files. +- [ ] `server`: The function to get the server files. +- [ ] `dependencies`: The function to get the dependencies. +- [ ] `all`: The function to get all the static files (static + assets + client + public). + +```js +const staticFiles = await files.static(); +``` + +The `copy` object contains the following functions: + +- [ ] `static`: The function to copy the static files. +- [ ] `assets`: The function to copy the assets files. +- [ ] `client`: The function to copy the client files. +- [ ] `public`: The function to copy the public files. +- [ ] `server`: The function to copy the server files. +- [ ] `dependencies`: The function to copy the dependencies. + +```js +await copy.server(outServerDir); +``` + +## Helper functions + +### banner + +Shows a banner in the console. + +```js +banner("building serverless functions"); +``` + +### message + +Shows a message in the console. Primary and secondary colors used to show the action and the message. + +```js +message("creating", "index.func module"); +``` + +### success + +Shows a success message in the console. + +```js +success("index.func serverless function initialized."); +``` + +### clearDirectory + +Clears a directory. + +```js +await clearDirectory(outServerDir); +``` + +### writeJSON + +Writes a JSON file. + +```js +await writeJSON(join(outServerDir, ".vc-config.json"), { + runtime: "nodejs20.x", + handler: "index.mjs", + launcherType: "Nodejs", + shouldAddHelpers: true, + supportsResponseStreaming: true, + ...adapterOptions?.serverlessFunctions?.index, +}); +``` diff --git a/docs/src/pages/en/deploy.(index).mdx b/docs/src/pages/en/deploy.(index).mdx index 099af35..58d03c4 100644 --- a/docs/src/pages/en/deploy.(index).mdx +++ b/docs/src/pages/en/deploy.(index).mdx @@ -4,4 +4,6 @@ When you're finished with your app, you can deploy it to the web. The framework You will learn how to use [adapters](/deploy/adapters) to configure your app for different deployment environments. -You can also learn how to deploy your app to different platforms using the available adapters. The framework provides adapters for [Vercel](/deploy/vercel) right now, but there are more coming soon to deploy your app to Netlify, Cloudflare Pages, Cloudflare Workers, or Serverless Stack. \ No newline at end of file +You can also learn how to deploy your app to different platforms using the available adapters. The framework provides adapters for [Vercel](/deploy/vercel) right now, but there are more coming soon to deploy your app to Netlify, Cloudflare Pages, Cloudflare Workers, or Serverless Stack. + +Find more information about how to implement deployment adapters in the [Adapter API](/deploy/api) section. \ No newline at end of file diff --git a/packages/react-server-adapter-core/LICENSE b/packages/react-server-adapter-core/LICENSE new file mode 100644 index 0000000..ec9aa09 --- /dev/null +++ b/packages/react-server-adapter-core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Viktor Lázár + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/react-server-adapter-core/README.md b/packages/react-server-adapter-core/README.md new file mode 100644 index 0000000..b6d970c --- /dev/null +++ b/packages/react-server-adapter-core/README.md @@ -0,0 +1,5 @@ +# @lazarv/react-server-adapter-core + +Core library to develop deployment adapters for [@lazarv/react-server](https://npmjs.com/package/@lazarv/react-server). + +See details at https://react-server.dev. diff --git a/packages/react-server-adapter-core/index.d.ts b/packages/react-server-adapter-core/index.d.ts new file mode 100644 index 0000000..503fa34 --- /dev/null +++ b/packages/react-server-adapter-core/index.d.ts @@ -0,0 +1,81 @@ +declare module "@lazarv/react-server-adapter-core" { + export interface Adapter { + (adapterOptions: T, root: string, options: any): Promise; + } + + export function createAdapter(options: { + name: string; + outDir: string; + outStaticDir?: string; + handler: (options: { + adapterOptions: T; + files: { + static: () => Promise; + assets: () => Promise; + client: () => Promise; + public: () => Promise; + server: () => Promise; + dependencies: ( + adapterFiles: string[] + ) => Promise<{ src: string; dest: string }[]>; + all: () => Promise; + }; + copy: { + static: (out: string) => Promise; + assets: (out: string) => Promise; + client: (out: string) => Promise; + public: (out: string) => Promise; + server: (out: string) => Promise; + dependencies: (out: string, adapterFiles: string[]) => Promise; + }; + config: Record; + reactServerDir: string; + reactServerOutDir: string; + root: string; + options: any; + }) => Promise; + }): Adapter; + + export function banner(message: string): void; + export function clearDirectory(dir: string): Promise; + export function copy(src: string, dest: string): Promise; + export function copyMessage( + file: string, + srcDir: string, + destDir: string, + reactServerOutDir: string + ): void; + export function copyFiles( + message: string, + files: string[], + srcDir: string, + destDir: string, + reactServerOutDir: string + ): Promise; + export function message(primary: string, secondary?: string): void; + export function success(message: string): void; + export function writeJSON( + path: string, + data: Record + ): Promise; + export function clearProgress(): void; + export function createProgress( + message: string, + total: number, + start?: number + ): void; + export function progress(options: { + message: string; + files: string[]; + onProgress: (file: string) => Promise; + onFile: (file: string) => Promise; + }): Promise; + export function getConfig(): Record; + export function getPublicDir(): string; + export function getFiles(pattern: string, srcDir?: string): Promise; + export function getDependencies( + adapterFiles: string[], + reactServerDir: string + ): Promise; + export function spawnCommand(command: string, args: string[]): Promise; +} diff --git a/packages/react-server-adapter-core/index.mjs b/packages/react-server-adapter-core/index.mjs new file mode 100644 index 0000000..677633e --- /dev/null +++ b/packages/react-server-adapter-core/index.mjs @@ -0,0 +1,471 @@ +import { spawn } from "node:child_process"; +import { lstatSync, readlinkSync } from "node:fs"; +import { cp, rm, writeFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { + basename, + dirname, + extname, + isAbsolute, + join, + relative, +} from "node:path"; + +import { moduleAliases } from "@lazarv/react-server/lib/loader/module-alias.mjs"; +import * as sys from "@lazarv/react-server/lib/sys.mjs"; +import packageJson from "@lazarv/react-server/package.json" with { type: "json" }; +import { getContext } from "@lazarv/react-server/server/context.mjs"; +import { + CONFIG_CONTEXT, + CONFIG_ROOT, +} from "@lazarv/react-server/server/symbols.mjs"; +import { nodeFileTrace, resolve } from "@vercel/nft"; +import cliProgress from "cli-progress"; +import spinners from "cli-spinners"; +import glob from "fast-glob"; +import logUpdate from "log-update"; +import colors from "picocolors"; + +const __require = createRequire(import.meta.url); +const cwd = sys.cwd(); + +const PROGRESS_LIMIT = + process.env.CI || process.env.REACT_SERVER_PROGRESS_LIMIT + ? Infinity + : process.env.REACT_SERVER_PROGRESS_LIMIT + ? parseInt(process.env.REACT_SERVER_PROGRESS_LIMIT) || 50 + : 50; + +let interval; + +const oldConsoleLog = console.log; +console.log = function (...args) { + clearInterval(interval); + oldConsoleLog(...args); +}; + +export function banner(message) { + const spinner = spinners.bouncingBar; + console.log(); + + logUpdate( + `${colors.cyan(`${packageJson.name.split("/").pop()}/${packageJson.version}`)} ${colors.green(message)}` + ); + + let i = -1; + interval = setInterval(() => { + i = ++i % spinner.frames.length; + logUpdate( + `${colors.cyan(`${packageJson.name.split("/").pop()}/${packageJson.version}`)} ${colors.green(message)} ${colors.magenta(spinner.frames[i])}` + ); + }, spinner.interval); +} + +export function createProgress(message, total, start = 0) { + if (process.env.CI || total < PROGRESS_LIMIT) { + return null; + } + + clearInterval(interval); + const progress = new cliProgress.SingleBar({ + format: `${message} ${colors.magenta("[{bar}]")} {percentage}%${colors.gray(" | ETA: {eta}s | {value}/{total}")}`, + }); + progress.start(total, start); + return progress; +} + +export function clearProgress() { + clearInterval(interval); +} + +export function getConfig() { + return getContext(CONFIG_CONTEXT)?.[CONFIG_ROOT]; +} + +export function getPublicDir() { + const config = getConfig(); + return join( + cwd, + typeof config.public === "string" ? config.public : "public" + ); +} + +export async function clearDirectory(dir) { + await rm(dir, { recursive: true, force: true }); +} + +export async function getFiles(pattern, srcDir = cwd) { + return glob(pattern, { + onlyFiles: true, + cwd: srcDir, + }); +} + +export function message(primary, secondary) { + if (!secondary) { + console.log(primary); + } else if (primary && secondary) { + console.log(`${primary} ${colors.gray(secondary)}`); + } else { + console.log(); + } +} + +export function success(message) { + console.log(`${colors.green("✓")} ${message}\n`); +} + +const extensionColor = { + ".json": "magenta", + ".css": "magenta", +}; +export function copyMessage(file, srcDir, destDir, reactServerOutDir) { + console.log( + `copy ${colors + .gray( + `${relative(cwd, reactServerOutDir)}/${relative(reactServerOutDir, srcDir)}/${colors.cyan(file)}`.replace( + /^\/+/, + "" + ) + ) + .replace( + /\/+/g, + "/" + )} => ${colors.gray(`${relative(cwd, destDir)}/${colors[extensionColor[extname(file)] ?? "cyan"](file)}`).replace(/^\/+/, "")}` + ); +} + +export function copy(srcDir, destDir, reactServerOutDir) { + return async (file) => { + const src = join(srcDir, file); + const dest = join(destDir, file); + copyMessage(file, srcDir, destDir, reactServerOutDir); + await cp(src, dest); + }; +} + +export async function progress({ message, files, onProgress, onFile }) { + const progress = createProgress(message, files.length); + const promises = files.map( + progress + ? async (file) => { + progress.increment(); + return onProgress(file); + } + : onFile + ); + await Promise.all(promises); + progress?.stop(); + success(`${files.length} files copied.`); +} + +export async function copyFiles( + message, + files, + srcDir, + destDir, + reactServerOutDir +) { + if (files.length > 0) { + banner(message); + await Promise.all(files.map(copy(srcDir, destDir, reactServerOutDir))); + success(`${files.length} files copied.`); + } +} + +export async function writeJSON(file, data) { + return writeFile(file, JSON.stringify(data, null, 2)); +} + +export async function getDependencies(adapterFiles, reactServerDir) { + let rootDir = cwd; + let lockFile = []; + while (lockFile.length === 0) { + lockFile = await glob( + ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"], + { + onlyFiles: true, + cwd: rootDir, + } + ); + if (lockFile.length > 0) { + break; + } + rootDir = join(rootDir, ".."); + if (rootDir === "/") { + rootDir = cwd; + break; + } + } + + const sourceFiles = await glob("server/**/*.mjs", { + onlyFiles: true, + absolute: true, + cwd: reactServerDir, + }); + const reactServerDeps = [ + __require.resolve("@lazarv/react-server/lib/start/render-stream.mjs", { + paths: [cwd], + }), + __require.resolve("@lazarv/react-server/lib/loader/node-loader.mjs", { + paths: [cwd], + }), + __require.resolve( + "@lazarv/react-server/lib/loader/node-loader.react-server.mjs", + { + paths: [cwd], + } + ), + __require.resolve("@lazarv/react-server/client/entry.client.jsx", { + paths: [cwd], + }), + ]; + sourceFiles.push(...adapterFiles, ...reactServerDeps); + + const reactServerPkgDir = dirname( + __require.resolve("@lazarv/react-server/package.json", { + paths: [cwd], + }) + ); + + const traceCache = {}; + const aliasReactServer = moduleAliases("react-server"); + const aliasReact = moduleAliases(); + const traces = await Promise.all([ + nodeFileTrace(sourceFiles, { + conditions: ["react-server", "node", "import"], + cache: traceCache, + base: rootDir, + ignore: [`${reactServerPkgDir}/lib/dev/create-logger.mjs`], + resolve(id, parent, job, cjsResolve) { + if (aliasReactServer[id]) { + return aliasReactServer[id]; + } + return resolve(id, parent, job, cjsResolve); + }, + }), + nodeFileTrace(sourceFiles, { + conditions: ["node", "import"], + cache: traceCache, + base: rootDir, + ignore: [`${reactServerPkgDir}/lib/dev/create-logger.mjs`], + resolve(id, parent, job, cjsResolve) { + if (aliasReact[id]) { + return aliasReact[id]; + } + return resolve(id, parent, job, cjsResolve); + }, + }), + ]); + + const trace = traces.reduce((trace, t) => { + t.fileList.forEach((file) => trace.add(file)); + t.esmFileList.forEach((file) => trace.add(file)); + return trace; + }, new Set()); + + reactServerDeps.forEach((file) => trace.add(relative(rootDir, file))); + const dependencyFiles = Array.from(trace).reduce((deps, file) => { + const src = join(rootDir, file); + const stat = lstatSync(src); + if (stat.isSymbolicLink()) { + const srcLink = readlinkSync(src); + const link = isAbsolute(srcLink) ? srcLink : join(dirname(src), srcLink); + const linkStat = lstatSync(link); + if (linkStat.isDirectory()) { + return deps; + } + } + if ( + stat.isDirectory() || + (sourceFiles.includes(src) && !reactServerDeps.includes(src)) + ) { + return deps; + } + if (!deps.includes(src)) { + deps.push(src); + } + return deps; + }, []); + + return dependencyFiles.map((src) => { + const path = sys.normalizePath(relative(rootDir, src)); + const dest = path.startsWith("node_modules/.pnpm") + ? path.split("/").slice(3).join("/") + : path.startsWith(sys.normalizePath(relative(rootDir, reactServerPkgDir))) + ? path.replace( + sys.normalizePath(relative(rootDir, reactServerPkgDir)), + "node_modules/@lazarv/react-server" + ) + : path; + return { src, dest }; + }); +} + +export async function spawnCommand(command, args) { + const deploy = spawn(command, args, { + cwd, + stdio: "inherit", + }); + await new Promise((resolve, reject) => { + deploy.on("exit", (code) => { + if (code === 0) { + resolve(); + } else { + reject(); + } + }); + }); +} + +export function createAdapter({ + name, + outDir, + outStaticDir, + outServerDir, + handler, + deploy, +}) { + return async function (adapterOptions, root, options) { + adapterOptions = adapterOptions ?? {}; + const reactServerOutDir = options.outDir ?? ".react-server"; + + const reactServerDir = join(cwd, reactServerOutDir); + const distDir = join(reactServerDir, "dist"); + + const config = getConfig(); + const publicDir = getPublicDir(); + + banner(`building ${name} output`); + console.log( + `preparing ${colors.gray(`${relative(cwd, outDir)} for deployment`)}` + ); + await clearDirectory(outDir); + success(`${name} output successfully prepared.`); + + const files = { + static: () => + getFiles( + ["**/*", "!**/*.html.gz", "!**/*.html.br", "!**/x-component.*"], + distDir + ), + assets: () => getFiles(["assets/**/*"], reactServerDir), + client: () => + getFiles(["client/**/*", "!**/*-manifest.json"], reactServerDir), + public: () => getFiles(["**/*"], publicDir), + server: () => + getFiles(["**/*-manifest.json", "server/**/*.mjs"], reactServerDir), + dependencies: (adapterFiles) => + getDependencies(adapterFiles, reactServerDir), + all: async () => + ( + await Promise.all([ + files.static(), + files.assets(), + files.client(), + files.public(), + files.server(), + ]) + ).flat(), + }; + + const copy = { + static: async (out) => + copyFiles( + "copying static files", + await files.static(), + distDir, + out ?? outStaticDir, + reactServerDir + ), + assets: async (out) => + copyFiles( + "copying assets", + await files.assets(), + reactServerDir, + out ?? outStaticDir, + reactServerDir + ), + client: async (out) => + copyFiles( + "copying client components", + await files.client(), + reactServerDir, + out ?? outStaticDir, + reactServerDir + ), + public: async (out) => + copyFiles( + "copying public", + await files.public(), + publicDir, + out ?? outStaticDir, + cwd + ), + server: async (out) => + copyFiles( + "copying server files", + await files.server(), + reactServerDir, + join(out ?? outServerDir, ".react-server"), + reactServerOutDir + ), + dependencies: async (out, adapterFiles) => { + const copyDependency = async ({ src, dest }) => { + await cp(src, join(out, dest)); + }; + + banner("copying server dependencies"); + await progress({ + message: "copying dependencies", + files: await files.dependencies(adapterFiles ?? []), + onProgress: copyDependency, + onFile: async (file) => { + console.log( + `copy ${colors.gray(dirname(relative(cwd, file.src).replace(/^(\.\.\/)+/g, "")))}/${colors.cyan(basename(file.src))} => ${colors.gray(relative(cwd, out))}/${colors.cyan(file.dest)}` + ); + await copyDependency(file); + }, + }); + }, + }; + + if (outStaticDir) { + await copy.static(); + await copy.assets(); + await copy.client(); + await copy.public(); + } + + if (outServerDir) { + await copy.server(); + } + + await handler({ + files, + copy, + config, + adapterOptions, + reactServerDir, + reactServerOutDir, + root, + options, + }); + + success(`${name} deployment successfully created.`); + if (deploy && deploy.command && deploy.args) { + if (options.deploy) { + banner(`deploying to ${name}`); + clearProgress(); + await spawnCommand(deploy.command, deploy.args); + } else { + console.log( + `${colors.gray(`Deploy to ${name} using:`)} ${deploy.command} ${deploy.args.join(" ")}` + ); + if (deploy.message) { + console.log(deploy.message); + } + } + } + }; +} diff --git a/packages/react-server-adapter-core/package.json b/packages/react-server-adapter-core/package.json new file mode 100644 index 0000000..7af2d3d --- /dev/null +++ b/packages/react-server-adapter-core/package.json @@ -0,0 +1,46 @@ +{ + "name": "@lazarv/react-server-adapter-core", + "version": "0.0.0", + "description": "React Server Adapter core library", + "module": "index.mjs", + "type": "module", + "sideEffects": true, + "exports": { + ".": { + "import": "./index.mjs", + "types": "./index.d.ts" + } + }, + "scripts": {}, + "keywords": [ + "react", + "ssr", + "esm", + "server", + "serverless", + "deployment" + ], + "author": "lazarv", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/lazarv/react-server.git" + }, + "bugs": { + "url": "https://github.com/lazarv/react-server/issues" + }, + "dependencies": { + "@vercel/nft": "^0.27.2", + "cli-progress": "^3.12.0", + "cli-spinners": "^3.0.0", + "fast-glob": "^3.2.12", + "log-update": "^6.0.0", + "picocolors": "^1.0.0" + }, + "devDependencies": { + "@lazarv/react-server": "workspace:^" + }, + "peerDependencies": { + "@lazarv/react-server": "workspace:^" + } +} diff --git a/packages/react-server-adapter-vercel/index.mjs b/packages/react-server-adapter-vercel/index.mjs index 7b00379..1772cda 100644 --- a/packages/react-server-adapter-vercel/index.mjs +++ b/packages/react-server-adapter-vercel/index.mjs @@ -1,434 +1,83 @@ -import { spawn } from "node:child_process"; -import { lstatSync, readlinkSync } from "node:fs"; -import { cp, rm, writeFile } from "node:fs/promises"; -import { createRequire } from "node:module"; -import { basename, dirname, isAbsolute, join, relative } from "node:path"; +import { cp } from "node:fs/promises"; +import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { moduleAliases } from "@lazarv/react-server/lib/loader/module-alias.mjs"; import * as sys from "@lazarv/react-server/lib/sys.mjs"; -import packageJson from "@lazarv/react-server/package.json" with { type: "json" }; -import { getContext } from "@lazarv/react-server/server/context.mjs"; import { - CONFIG_CONTEXT, - CONFIG_ROOT, -} from "@lazarv/react-server/server/symbols.mjs"; -import { nodeFileTrace, resolve } from "@vercel/nft"; -import cliProgress from "cli-progress"; -import spinners from "cli-spinners"; -import glob from "fast-glob"; -import logUpdate from "log-update"; -import colors from "picocolors"; + banner, + clearDirectory, + createAdapter, + message, + success, + writeJSON, +} from "@lazarv/react-server-adapter-core"; -const __require = createRequire(import.meta.url); const cwd = sys.cwd(); const vercelDir = join(cwd, ".vercel"); const outDir = join(vercelDir, "output"); const outStaticDir = join(outDir, "static"); const adapterDir = dirname(fileURLToPath(import.meta.url)); -const PROGRESS_LIMIT = 50; - -let interval; - -const oldConsoleLog = console.log; -console.log = function (...args) { - clearInterval(interval); - oldConsoleLog(...args); -}; - -function banner(message) { - const spinner = spinners.bouncingBar; - console.log(); - - logUpdate( - `${colors.cyan(`${packageJson.name.split("/").pop()}/${packageJson.version}`)} ${colors.green(message)}` - ); - - let i = -1; - interval = setInterval(() => { - i = ++i % spinner.frames.length; - logUpdate( - `${colors.cyan(`${packageJson.name.split("/").pop()}/${packageJson.version}`)} ${colors.green(message)} ${colors.magenta(spinner.frames[i])}` - ); - }, spinner.interval); -} - -function createProgress(message, total, start = 0) { - if (process.env.CI || total < PROGRESS_LIMIT) { - return null; - } - - clearInterval(interval); - const progress = new cliProgress.SingleBar({ - format: `${message} ${colors.magenta("[{bar}]")} {percentage}%${colors.gray(" | ETA: {eta}s | {value}/{total}")}`, - }); - progress.start(total, start); - return progress; -} - -export async function adapter(adapterOptions, root, options) { - const reactServerOutDir = options.outDir ?? ".react-server"; - const reactServerDir = join(cwd, reactServerOutDir); - const distDir = join(reactServerDir, "dist"); - const clientDir = join(reactServerDir, "client"); - const assetsDir = join(reactServerDir, "assets"); - - const config = getContext(CONFIG_CONTEXT)?.[CONFIG_ROOT]; - const publicDir = join( - cwd, - typeof config.public === "string" ? config.public : "public" - ); - - banner("building Vercel output"); - console.log( - `preparing ${colors.gray("preparing .vercel/output for deployment")}\n` - ); - await rm(outDir, { recursive: true, force: true }); - - const distFiles = await glob( - ["**/*", "!**/*.html.gz", "!**/*.html.br", "!**/x-component.*"], - { - onlyFiles: true, - cwd: distDir, - } - ); - if (distFiles.length > 0) { - banner("copying static files"); - await Promise.all( - distFiles.map(async (file) => { - const src = join(distDir, file); - const dest = join(outStaticDir, file); - console.log( - `copy ${colors.gray(`${reactServerOutDir}/dist/${colors.cyan(file)}`)} => ${colors.gray(`.vercel/output/static/${colors.cyan(file)}`)}` - ); - await cp(src, dest); - }) - ); - console.log(`${colors.green("✓")} ${distFiles.length} files copied.\n`); - } - - const assetFiles = await glob("**/*", { - onlyFiles: true, - cwd: assetsDir, - }); - if (assetFiles.length > 0) { - banner("copying assets"); - await Promise.all( - assetFiles.map(async (file) => { - const src = join(assetsDir, file); - const dest = join(outStaticDir, "assets", file); - console.log( - `copy ${colors.gray(`${reactServerOutDir}/assets/${colors.cyan(file)}`)} => ${colors.gray(`.vercel/output/static/assets/${colors.cyan(file)}`)}` - ); - await cp(src, dest); - }) - ); - console.log(`${colors.green("✓")} ${assetFiles.length} files copied.\n`); - } - - const clientFiles = await glob("**/*", { - onlyFiles: true, - cwd: clientDir, - }); - if (clientFiles.length > 0) { - banner("copying client components"); - await Promise.all( - clientFiles.map(async (file) => { - const src = join(clientDir, file); - const dest = join(outStaticDir, "client", file); - console.log( - `copy ${colors.gray(`${reactServerOutDir}/client/${colors.cyan(file)}`)} => ${colors.gray(`.vercel/output/static/client/${colors.cyan(file)}`)}` - ); - await cp(src, dest); - }) - ); - console.log(`${colors.green("✓")} ${clientFiles.length} files copied.\n`); - } - - if (config.public !== false) { - const publicFiles = await glob("**/*", { - onlyFiles: true, - cwd: publicDir, - }); - if (publicFiles.length > 0) { - banner("copying public"); - await Promise.all( - publicFiles.map(async (file) => { - const src = join(publicDir, file); - const dest = join(outStaticDir, file); - console.log( - `copy ${colors.gray(`${relative(cwd, publicDir)}/${colors.cyan(file)}`)} => ${colors.gray(`.vercel/output/static/${colors.cyan(file)}`)}` - ); - await cp(src, dest); - }) - ); - console.log(`${colors.green("✓")} ${publicFiles.length} files copied.\n`); - } - } +export const adapter = createAdapter({ + name: "Vercel", + outDir, + outStaticDir, + handler: async function ({ adapterOptions, copy }) { + if (adapterOptions?.serverlessFunctions !== false) { + banner("building serverless functions"); + + message("creating", "index.func module"); + const outServerDir = join(outDir, "functions/index.func"); + const entryFile = join(outServerDir, "index.mjs"); + + await clearDirectory(outServerDir); + await cp(join(adapterDir, "functions/index.mjs"), entryFile); + + message("creating", "index.func configuration"); + await writeJSON(join(outServerDir, ".vc-config.json"), { + runtime: "nodejs20.x", + handler: "index.mjs", + launcherType: "Nodejs", + shouldAddHelpers: true, + supportsResponseStreaming: true, + ...adapterOptions?.serverlessFunctions?.index, + }); + success("index.func serverless function initialized."); - if (adapterOptions?.serverlessFunctions !== false) { - banner("building serverless functions"); - console.log(`creating ${colors.gray("creating index.func module")}`); - await rm(join(cwd, outDir, "functions/index.func"), { - recursive: true, - force: true, - }); - await cp( - join(adapterDir, "functions/index.mjs"), - join(outDir, "functions/index.func/index.mjs") - ); + await copy.server(outServerDir); + await copy.dependencies(outServerDir, [entryFile]); - console.log( - `creating ${colors.gray("creating index.func configuration")}\n` - ); - await writeFile( - join(outDir, "functions/index.func/.vc-config.json"), - JSON.stringify( + adapterOptions.routes = [ { - runtime: "nodejs20.x", - handler: "index.mjs", - launcherType: "Nodejs", - shouldAddHelpers: true, - supportsResponseStreaming: true, - ...adapterOptions?.serverlessFunctions?.index, + src: "^/(.*)", + dest: "/", }, - null, - 2 - ), - "utf8" - ); - - banner("copying server files"); - const buildFiles = await glob(["**/*-manifest.json", "server/**/*.mjs"], { - onlyFiles: true, - cwd: reactServerDir, - }); - if (buildFiles.length > 0) { - await Promise.all( - buildFiles.map(async (file) => { - const src = join(reactServerDir, file); - const dest = join(outDir, "functions/index.func/.react-server", file); - console.log( - `copy ${colors.gray(`${relative(cwd, reactServerDir)}/${colors.cyan(file)}`)} => ${colors.gray(`.vercel/output/static/${colors.cyan(file)}`)}` - ); - await cp(src, dest); - }) - ); - console.log(`${colors.green("✓")} ${buildFiles.length} files copied.\n`); - } - - banner("copying server dependencies"); - - let rootDir = cwd; - let lockFile = []; - while (lockFile.length === 0) { - lockFile = await glob( - ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"], - { - onlyFiles: true, - cwd: rootDir, - } - ); - if (lockFile.length > 0) { - break; - } - rootDir = join(rootDir, ".."); - if (rootDir === "/") { - rootDir = cwd; - break; - } + ...(adapterOptions.routes ?? []), + ]; } - const sourceFiles = await glob("server/**/*.mjs", { - onlyFiles: true, - absolute: true, - cwd: reactServerDir, - }); - const reactServerDeps = [ - __require.resolve("@lazarv/react-server/lib/start/render-stream.mjs", { - paths: [cwd], - }), - __require.resolve("@lazarv/react-server/lib/loader/node-loader.mjs", { - paths: [cwd], - }), - __require.resolve( - "@lazarv/react-server/lib/loader/node-loader.react-server.mjs", - { - paths: [cwd], - } - ), - __require.resolve("@lazarv/react-server/client/entry.client.jsx", { - paths: [cwd], - }), - ]; - sourceFiles.push( - join(outDir, "functions/index.func/index.mjs"), - ...reactServerDeps - ); - - const reactServerPkgDir = dirname( - __require.resolve("@lazarv/react-server/package.json", { - paths: [cwd], - }) - ); - - const traceCache = {}; - const aliasReactServer = moduleAliases("react-server"); - const aliasReact = moduleAliases(); - const traces = await Promise.all([ - nodeFileTrace(sourceFiles, { - conditions: ["react-server", "node", "import"], - cache: traceCache, - base: rootDir, - ignore: [`${reactServerPkgDir}/lib/dev/create-logger.mjs`], - resolve(id, parent, job, cjsResolve) { - if (aliasReactServer[id]) { - return aliasReactServer[id]; - } - return resolve(id, parent, job, cjsResolve); + banner("creating deployment configuration"); + message("creating", "config.json"); + await writeJSON(join(outDir, "config.json"), { + version: 3, + ...adapterOptions, + routes: [ + { handle: "filesystem" }, + ...(adapterOptions?.routes ?? []), + adapterOptions?.routes?.find((route) => route.status === 404) ?? { + src: "/(.*)", + status: 404, + dest: "/404/index.html", }, - }), - nodeFileTrace(sourceFiles, { - conditions: ["node", "import"], - cache: traceCache, - base: rootDir, - ignore: [`${reactServerPkgDir}/lib/dev/create-logger.mjs`], - resolve(id, parent, job, cjsResolve) { - if (aliasReact[id]) { - return aliasReact[id]; - } - return resolve(id, parent, job, cjsResolve); - }, - }), - ]); - - const trace = traces.reduce((trace, t) => { - t.fileList.forEach((file) => trace.add(file)); - t.esmFileList.forEach((file) => trace.add(file)); - return trace; - }, new Set()); - - reactServerDeps.forEach((file) => trace.add(relative(rootDir, file))); - const dependencyFiles = Array.from(trace).reduce((deps, file) => { - const src = join(rootDir, file); - const stat = lstatSync(src); - if (stat.isSymbolicLink()) { - const srcLink = readlinkSync(src); - const link = isAbsolute(srcLink) - ? srcLink - : join(dirname(src), srcLink); - const linkStat = lstatSync(link); - if (linkStat.isDirectory()) { - return deps; - } - } - if ( - stat.isDirectory() || - (sourceFiles.includes(src) && !reactServerDeps.includes(src)) - ) { - return deps; - } - if (!deps.includes(src)) { - deps.push(src); - } - return deps; - }, []); - - const dependencyProgress = createProgress( - `copying ${colors.gray("dependencies")}`, - dependencyFiles.length - ); - for (const src of dependencyFiles) { - const path = sys.normalizePath(relative(rootDir, src)); - const dest = join( - outDir, - "functions/index.func", - path.startsWith("node_modules/.pnpm") - ? path.split("/").slice(3).join("/") - : path.startsWith( - sys.normalizePath(relative(rootDir, reactServerPkgDir)) - ) - ? path.replace( - sys.normalizePath(relative(rootDir, reactServerPkgDir)), - "node_modules/@lazarv/react-server" - ) - : path - ); - if (dependencyProgress) { - dependencyProgress.increment(); - } else { - console.log( - `copy ${colors.gray(`${relative(rootDir, dirname(src))}/${colors.cyan(basename(src))}`)} => ${colors.gray(`.vercel/output/${relative(outDir, dirname(dest))}/${colors.cyan(basename(dest))}`)}` - ); - } - await cp(src, dest); - } - dependencyProgress?.stop(); - console.log( - `${colors.green("✓")} ${dependencyFiles.length} files copied.\n` - ); - - if (!adapterOptions) { - adapterOptions = {}; - } - adapterOptions.routes = [ - { - src: "^/(.*)", - dest: "/", - }, - ...(adapterOptions.routes ?? []), - ]; - } - - banner("creating deployment configuration"); - console.log(`creating ${colors.gray("config.json")}`); - await writeFile( - join(outDir, "config.json"), - JSON.stringify( - { - version: 3, - ...adapterOptions, - routes: [ - { handle: "filesystem" }, - ...(adapterOptions?.routes ?? []), - adapterOptions?.routes?.find((route) => route.status === 404) ?? { - src: "/(.*)", - status: 404, - dest: "/404/index.html", - }, - ], - }, - null, - 2 - ), - "utf8" - ); - - console.log(`\n${colors.green("✓")} Vercel deployment successfully created.`); - if (options.deploy) { - banner("deploying to Vercel"); - clearInterval(interval); - - const deploy = spawn("vercel", ["deploy", "--prebuilt"], { - cwd, - stdio: "inherit", + ], }); - await new Promise((resolve, reject) => { - deploy.on("exit", (code) => { - if (code === 0) { - resolve(); - } else { - reject(); - } - }); - }); - } else { - console.log( - `${colors.gray("Deploy to Vercel using:")} vercel deploy --prebuilt` - ); - } -} + success("configuration created."); + }, + deploy: { + command: "vercel", + args: ["deploy", "--prebuilt"], + }, +}); export default function defineConfig(adapterOptions) { return async (_, root, options) => adapter(adapterOptions, root, options); diff --git a/packages/react-server-adapter-vercel/package.json b/packages/react-server-adapter-vercel/package.json index e027147..b49b78f 100644 --- a/packages/react-server-adapter-vercel/package.json +++ b/packages/react-server-adapter-vercel/package.json @@ -25,18 +25,12 @@ "bugs": { "url": "https://github.com/lazarv/react-server/issues" }, - "dependencies": { - "@vercel/nft": "^0.27.2", - "cli-progress": "^3.12.0", - "cli-spinners": "^3.0.0", - "fast-glob": "^3.2.12", - "log-update": "^6.0.0", - "picocolors": "^1.0.0" - }, "devDependencies": { - "@lazarv/react-server": "workspace:^" + "@lazarv/react-server": "workspace:^", + "@lazarv/react-server-adapter-core": "workspace:^" }, "peerDependencies": { - "@lazarv/react-server": "workspace:^" + "@lazarv/react-server": "workspace:^", + "@lazarv/react-server-adapter-core": "workspace:^" } } diff --git a/packages/react-server/lib/build/action.mjs b/packages/react-server/lib/build/action.mjs index 363fdac..e38562c 100644 --- a/packages/react-server/lib/build/action.mjs +++ b/packages/react-server/lib/build/action.mjs @@ -1,6 +1,6 @@ +import { rm } from "node:fs/promises"; import { join } from "node:path"; import colors from "picocolors"; -import { rimraf } from "rimraf"; import logo from "../../bin/logo.mjs"; import { loadConfig } from "../../config/index.mjs"; @@ -41,11 +41,20 @@ export default async function build(root, options) { } // empty out dir if (options.server && options.client) - await rimraf(join(cwd, options.outDir)); + await rm(join(cwd, options.outDir), { + recursive: true, + force: true, + }); else if (options.server) - await rimraf(join(cwd, options.outDir, "server")); + await rm(join(cwd, options.outDir, "server"), { + recursive: true, + force: true, + }); else if (options.client) - await rimraf(join(cwd, options.outDir, "client")); + await rm(join(cwd, options.outDir, "client"), { + recursive: true, + force: true, + }); // build server let buildOutput = false; if (options.server) { @@ -63,8 +72,17 @@ export default async function build(root, options) { (options.export || typeof config[CONFIG_ROOT]?.export !== "undefined") ) { - await rimraf(join(cwd, options.outDir, "dist")); + const start = Date.now(); + await rm(join(cwd, options.outDir, "dist"), { + recursive: true, + force: true, + }); await staticSiteGenerator(root, options); + console.log( + colors.green( + `✔ exported in ${formatDuration(Date.now() - start)}` + ) + ); } await adapter(root, options); if (buildOutput) { diff --git a/packages/react-server/lib/build/server.mjs b/packages/react-server/lib/build/server.mjs index 61bb53a..5d182b5 100644 --- a/packages/react-server/lib/build/server.mjs +++ b/packages/react-server/lib/build/server.mjs @@ -30,7 +30,7 @@ const cwd = sys.cwd(); export default async function serverBuild(root, options) { root ||= "@lazarv/react-server/file-router"; - banner("server", options.dev); + banner("rsc", options.dev); const config = forRoot(); const clientManifest = new Map(); const serverManifest = new Map(); @@ -281,6 +281,9 @@ export default async function serverBuild(root, options) { }, }; + // empty line + console.log(); + banner("ssr", options.dev); await viteBuild(viteConfigClientComponents); } else { await writeFile( diff --git a/packages/react-server/package.json b/packages/react-server/package.json index 4f8b2e3..8d11fda 100644 --- a/packages/react-server/package.json +++ b/packages/react-server/package.json @@ -103,12 +103,11 @@ "pino": "^8.14.1", "react": "0.0.0-experimental-58af67a8f8-20240628", "react-dom": "0.0.0-experimental-58af67a8f8-20240628", + "react-is": "0.0.0-experimental-58af67a8f8-20240628", "react-property": "^2.0.2", "react-server-dom-webpack": "0.0.0-experimental-58af67a8f8-20240628", "remark-frontmatter": "^5.0.0", "remark-mdx-frontmatter": "^4.0.0", - "react-is": "0.0.0-experimental-58af67a8f8-20240628", - "rimraf": "^5.0.1", "sass": "^1.63.6", "semver": "^7.6.3", "strip-ansi": "^7.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62cb0d2..ec3c142 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,7 +118,7 @@ importers: version: 1.0.12(next@14.2.8(@babel/core@7.24.7)(react-dom@19.0.0-rc-3208e73e-20240730(react@19.0.0-rc-a7d1240c-20240731))(react@19.0.0-rc-a7d1240c-20240731)(sass@1.77.6))(react@19.0.0-rc-a7d1240c-20240731) '@vitejs/plugin-react-swc': specifier: ^3.7.0 - version: 3.7.0(@swc/helpers@0.5.5)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.39))(terser@5.31.1)) + version: 3.7.0(@swc/helpers@0.5.5)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.45))(terser@5.31.1)) highlight.js: specifier: ^11.9.0 version: 11.9.0 @@ -136,14 +136,14 @@ importers: version: 4.0.0 vite-plugin-svgr: specifier: ^4.2.0 - version: 4.2.0(rollup@4.18.0)(typescript@5.5.2)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.39))(terser@5.31.1)) + version: 4.2.0(rollup@4.18.0)(typescript@5.5.2)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.45))(terser@5.31.1)) devDependencies: '@types/react': specifier: ^18.3.2 version: 18.3.3 autoprefixer: specifier: ^10.4.19 - version: 10.4.19(postcss@8.4.39) + version: 10.4.19(postcss@8.4.45) concurrently: specifier: ^8.2.2 version: 8.2.2 @@ -668,9 +668,6 @@ importers: remark-mdx-frontmatter: specifier: ^4.0.0 version: 4.0.0 - rimraf: - specifier: ^5.0.1 - version: 5.0.7 sass: specifier: ^1.63.6 version: 1.77.6 @@ -691,7 +688,7 @@ importers: specifier: ^20.10.0 version: 20.14.9 - packages/react-server-adapter-vercel: + packages/react-server-adapter-core: dependencies: '@vercel/nft': specifier: ^0.27.2 @@ -707,7 +704,7 @@ importers: version: 3.3.2 log-update: specifier: ^6.0.0 - version: 6.0.0 + version: 6.1.0 picocolors: specifier: ^1.0.0 version: 1.0.1 @@ -716,6 +713,15 @@ importers: specifier: workspace:^ version: link:../react-server + packages/react-server-adapter-vercel: + devDependencies: + '@lazarv/react-server': + specifier: workspace:^ + version: link:../react-server + '@lazarv/react-server-adapter-core': + specifier: workspace:^ + version: link:../react-server-adapter-core + test: dependencies: '@lazarv/react-server': @@ -3601,10 +3607,6 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-escapes@6.2.1: - resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} - engines: {node: '>=14.16'} - ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} @@ -3964,10 +3966,6 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -5814,10 +5812,6 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - log-update@6.0.0: - resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} - engines: {node: '>=18'} - log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -7128,10 +7122,6 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -7153,11 +7143,6 @@ packages: engines: {node: '>=14'} hasBin: true - rimraf@5.0.7: - resolution: {integrity: sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==} - engines: {node: '>=14.18'} - hasBin: true - rollup@4.18.0: resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -11427,10 +11412,10 @@ snapshots: next: 14.2.8(@babel/core@7.24.7)(react-dom@19.0.0-rc-3208e73e-20240730(react@19.0.0-rc-a7d1240c-20240731))(react@19.0.0-rc-a7d1240c-20240731)(sass@1.77.6) react: 19.0.0-rc-a7d1240c-20240731 - '@vitejs/plugin-react-swc@3.7.0(@swc/helpers@0.5.5)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.39))(terser@5.31.1))': + '@vitejs/plugin-react-swc@3.7.0(@swc/helpers@0.5.5)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.45))(terser@5.31.1))': dependencies: '@swc/core': 1.6.6(@swc/helpers@0.5.5) - vite: 6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.39))(terser@5.31.1) + vite: 6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.45))(terser@5.31.1) transitivePeerDependencies: - '@swc/helpers' @@ -11652,8 +11637,6 @@ snapshots: dependencies: type-fest: 0.21.3 - ansi-escapes@6.2.1: {} - ansi-escapes@7.0.0: dependencies: environment: 1.1.0 @@ -11803,6 +11786,16 @@ snapshots: postcss: 8.4.39 postcss-value-parser: 4.2.0 + autoprefixer@10.4.19(postcss@8.4.45): + dependencies: + browserslist: 4.23.1 + caniuse-lite: 1.0.30001638 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.45 + postcss-value-parser: 4.2.0 + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 @@ -12067,10 +12060,6 @@ snapshots: dependencies: restore-cursor: 3.1.0 - cli-cursor@4.0.0: - dependencies: - restore-cursor: 4.0.0 - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -14327,14 +14316,6 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - log-update@6.0.0: - dependencies: - ansi-escapes: 6.2.1 - cli-cursor: 4.0.0 - slice-ansi: 7.1.0 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.0 - log-update@6.1.0: dependencies: ansi-escapes: 7.0.0 @@ -16040,11 +16021,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - restore-cursor@4.0.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -16062,10 +16038,6 @@ snapshots: dependencies: glob: 9.3.5 - rimraf@5.0.7: - dependencies: - glob: 10.4.2 - rollup@4.18.0: dependencies: '@types/estree': 1.0.5 @@ -17041,12 +17013,12 @@ snapshots: - supports-color - terser - vite-plugin-svgr@4.2.0(rollup@4.18.0)(typescript@5.5.2)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.39))(terser@5.31.1)): + vite-plugin-svgr@4.2.0(rollup@4.18.0)(typescript@5.5.2)(vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.45))(terser@5.31.1)): dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.18.0) '@svgr/core': 8.1.0(typescript@5.5.2) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.2)) - vite: 6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.39))(terser@5.31.1) + vite: 6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.45))(terser@5.31.1) transitivePeerDependencies: - rollup - supports-color @@ -17081,20 +17053,6 @@ snapshots: sugarss: 4.0.1(postcss@8.4.45) terser: 5.31.1 - vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.39))(terser@5.31.1): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.45 - rollup: 4.18.0 - optionalDependencies: - '@types/node': 20.14.9 - fsevents: 2.3.3 - less: 4.2.0 - sass: 1.77.6 - stylus: 0.62.0 - sugarss: 4.0.1(postcss@8.4.39) - terser: 5.31.1 - vite@6.0.0-alpha.18(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.4.45))(terser@5.31.1): dependencies: esbuild: 0.21.5