diff --git a/examples/phoenix-liveview/Dockerfile b/examples/phoenix-liveview/Dockerfile new file mode 100644 index 0000000000..4622ad7dde --- /dev/null +++ b/examples/phoenix-liveview/Dockerfile @@ -0,0 +1,67 @@ +ARG ELIXIR_VERSION=1.17.2 +ARG OTP_VERSION=27.0.1 +ARG DEBIAN_VERSION=bookworm-20240722-slim + +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" + +FROM ${BUILDER_IMAGE} AS builder +LABEL maintainer="info@electric-sql.com" + +RUN apt-get update -y && \ + apt-get install -y build-essential git curl && \ + apt-get clean && \ + rm -f /var/lib/apt/lists/*_* + +RUN mix local.hex --force && mix local.rebar --force + +ENV MIX_ENV=prod + +WORKDIR /app + +COPY mix.* /app/ +COPY config/config.exs /app/config/ +COPY config/prod.exs /app/config/ +RUN mix deps.get --only $MIX_ENV --check-locked +RUN mix deps.compile +RUN mix assets.setup + +COPY config/*runtime.exs /app/config/ + +COPY lib /app/lib/ +RUN mix compile + +COPY assets /app/assets/ +COPY priv /app/priv/ +RUN mix assets.deploy + +COPY rel /app/rel/ + +RUN mix release + +FROM ${RUNNER_IMAGE} AS runner_setup + +RUN apt-get update -y && \ + apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates curl && \ + apt-get clean && \ + rm -f /var/lib/apt/lists/*_* + +# Set the locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen + +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en +ENV LC_ALL=en_US.UTF-8 + +WORKDIR "/app" +RUN chown nobody /app + +FROM runner_setup AS runner + +ARG RELEASE_NAME=electric_phoenix_example + +COPY --from=builder /app/_build/prod/rel/${RELEASE_NAME} ./ +RUN mv /app/bin/${RELEASE_NAME} /app/bin/entrypoint + +CMD ["/app/bin/server"] + diff --git a/examples/phoenix-liveview/config/runtime.exs b/examples/phoenix-liveview/config/runtime.exs index 58cd6f5be2..73b4fefea1 100644 --- a/examples/phoenix-liveview/config/runtime.exs +++ b/examples/phoenix-liveview/config/runtime.exs @@ -31,10 +31,12 @@ if config_env() == :prod do maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: [] config :electric_phoenix_example, Electric.PhoenixExample.Repo, - # ssl: true, url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), - socket_options: maybe_ipv6 + socket_options: maybe_ipv6, + ssl_opts: [ + verify: :verify_none + ] # The secret key base is used to sign/encrypt cookies and other secrets. # A default value is used in config/dev.exs and config/test.exs but you diff --git a/examples/phoenix-liveview/db/migrations/001-create_todos.sql b/examples/phoenix-liveview/db/migrations/001-create_todos.sql new file mode 100644 index 0000000000..15105cd262 --- /dev/null +++ b/examples/phoenix-liveview/db/migrations/001-create_todos.sql @@ -0,0 +1,7 @@ +CREATE TABLE todos ( + id BIGSERIAL PRIMARY KEY, + text VARCHAR(255) NOT NULL, + completed BOOLEAN NOT NULL DEFAULT FALSE, + inserted_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, + updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL +); diff --git a/examples/phoenix-liveview/lib/electric_phoenix_example/release.ex b/examples/phoenix-liveview/lib/electric_phoenix_example/release.ex new file mode 100644 index 0000000000..8e0b7176bf --- /dev/null +++ b/examples/phoenix-liveview/lib/electric_phoenix_example/release.ex @@ -0,0 +1,28 @@ +defmodule Electric.PhoenixExample.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :electric_phoenix_example + + def migrate do + load_app() + + for repo <- repos() do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end + + def rollback(repo, version) do + load_app() + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) + end + + defp repos do + Application.fetch_env!(@app, :ecto_repos) + end + + defp load_app do + Application.load(@app) + end +end diff --git a/examples/phoenix-liveview/package.json b/examples/phoenix-liveview/package.json new file mode 100644 index 0000000000..85a3d97246 --- /dev/null +++ b/examples/phoenix-liveview/package.json @@ -0,0 +1,15 @@ +{ + "name": "phoenix-liveview", + "version": "0.0.0", + "dependencies": { + "camelcase": "^8.0.0" + }, + "devDependencies": { + "@databases/pg-migrations": "^5.0.3", + "typescript": "5.7.2", + "sst": "3.3.7" + }, + "scripts": { + "deploy": "sst deploy --stage production" + } +} \ No newline at end of file diff --git a/examples/phoenix-liveview/rel/overlays/bin/migrate b/examples/phoenix-liveview/rel/overlays/bin/migrate new file mode 100755 index 0000000000..cf99fc0160 --- /dev/null +++ b/examples/phoenix-liveview/rel/overlays/bin/migrate @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +exec ./entrypoint eval Electric.PhoenixExample.Release.migrate diff --git a/examples/phoenix-liveview/rel/overlays/bin/server b/examples/phoenix-liveview/rel/overlays/bin/server new file mode 100755 index 0000000000..050783e96f --- /dev/null +++ b/examples/phoenix-liveview/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./entrypoint start diff --git a/examples/phoenix-liveview/sst-env.d.ts b/examples/phoenix-liveview/sst-env.d.ts new file mode 100644 index 0000000000..e4229cc1ad --- /dev/null +++ b/examples/phoenix-liveview/sst-env.d.ts @@ -0,0 +1,18 @@ +/* This file is auto-generated by SST. Do not edit. */ +/* tslint:disable */ +/* eslint-disable */ +/* deno-fmt-ignore-file */ +import "sst" +export {} +declare module "sst" { + export interface Resource { + "electricRegionVpc2EuWest1": { + "type": "sst.aws.Vpc" + } + "phoenixLiveviewServiceEuWest1": { + "service": string + "type": "sst.aws.Service" + "url": string + } + } +} diff --git a/examples/phoenix-liveview/sst.config.ts b/examples/phoenix-liveview/sst.config.ts new file mode 100644 index 0000000000..526b0fe9f7 --- /dev/null +++ b/examples/phoenix-liveview/sst.config.ts @@ -0,0 +1,165 @@ +/// + +import { execSync } from "child_process" +import camelcase from "camelcase" + +const isProduction = (stage) => stage.toLowerCase() === `production` + +const regionName = `eu-west-1` + +export default $config({ + app(input) { + return { + name: "phoenix-liveview", + removal: input?.stage === "production" ? "retain" : "remove", + protect: ["production"].includes(input?.stage), + home: "aws", + providers: { + cloudflare: `5.42.0`, + aws: { version: `6.57.0`, region: regionName }, + postgresql: `3.14.0`, + }, + } + }, + async run() { + if (!process.env.SECRET_KEY_BASE) { + throw new Error( + `Env variable SECRET_KEY_BASE must be set` + ) + } + + if (!process.env.ELECTRIC_API || !process.env.ELECTRIC_ADMIN_API) + throw new Error( + `Env variables ELECTRIC_API and ELECTRIC_ADMIN_API must be set` + ) + + if ( + !process.env.EXAMPLES_DATABASE_HOST || + !process.env.EXAMPLES_DATABASE_PASSWORD + ) { + throw new Error( + `Env variables EXAMPLES_DATABASE_HOST and EXAMPLES_DATABASE_PASSWORD must be set` + ) + } + + const provider = new postgresql.Provider(`neon`, { + host: process.env.EXAMPLES_DATABASE_HOST, + database: `neondb`, + username: `neondb_owner`, + password: process.env.EXAMPLES_DATABASE_PASSWORD, + }) + + const pg = new postgresql.Database(`liveview`, {}, { provider }) + + const pgBaseUri = $interpolate`postgresql://${provider.username}:${provider.password}@${provider.host}/${pg.name}` + const pgUriForElectric = $interpolate`${pgBaseUri}?sslmode=require` + const electricInfo = pgUriForElectric.apply((uri) => { + return addDatabaseToElectric(uri, `eu-west-1`) + }) + + const domainName = `phoenix-liveview${isProduction($app.stage) ? `` : `-stage-${$app.stage}`}.examples.electric-sql.com` + + // Run the server on ECS + const vpcName = camelcase(`electric-region-vpc-2-${regionName}`) + const vpc = new sst.aws.Vpc( + vpcName, + { + nat: $app.stage === `production` ? `managed` : `ec2`, + }, + { provider } + ) + + const cluster = new sst.aws.Cluster( + camelcase(`phoenix-liveview-cluster-${regionName}`), + { forceUpgrade: `v2`, vpc }, + { provider } + ) + + const liveviewService = cluster.addService( + camelcase(`phoenix-liveview-service-${regionName}`), + { + loadBalancer: { + public: true, + domain: { + name: domainName, + dns: sst.cloudflare.dns(), + }, + ports: [ + { listen: `443/https`, forward: `4000/http` }, + { listen: `80/http`, forward: `4000/http` }, + ], + }, + cpu: `0.25 vCPU`, + memory: `0.5 GB`, + // Uncomment the line below if you're trying to deploy from a Mac + //architecture: `arm64`, + transform: { + target: { + deregistrationDelay: 0, + }, + service: { + waitForSteadyState: true, + }, + }, + environment: { + DATABASE_URL: $interpolate`${pgBaseUri}?ssl=true`, + ELECTRIC_URL: `https://api-dev-production.electric-sql.com`, + SECRET_KEY_BASE: process.env.SECRET_KEY_BASE, + PHX_HOST: domainName, + ELECTRIC_CLIENT_PARAMS: $interpolate`{ "database_id": "${electricInfo.id}", "token": "${electricInfo.token}" }`, + }, + image: { + context: `.`, + dockerfile: `Dockerfile`, + }, + } + ) + + pgUriForElectric.apply(applyMigrations) + + return { + liveview: liveviewService.url, + databaseId: electricInfo.id, + token: electricInfo.token, + } + }, +}) + +function applyMigrations(uri: string) { + execSync(`pnpm exec pg-migrations apply --directory ./db/migrations`, { + env: { + ...process.env, + DATABASE_URL: uri, + }, + }) +} + +async function addDatabaseToElectric( + uri: string, + region: string +): Promise<{ + id: string + token: string +}> { + const adminApi = process.env.ELECTRIC_ADMIN_API + const url = new URL(`/v1/databases`, adminApi) + const result = await fetch(url, { + method: `PUT`, + headers: { "Content-Type": `application/json` }, + body: JSON.stringify({ + database_url: uri, + region, + }), + }) + if (!result.ok) { + throw new Error( + `Could not add database to Electric (${ + result.status + }): ${await result.text()}` + ) + } + return (await result.json()) as { + token: string + id: string + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf991e3a78..366fef853d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -589,6 +589,22 @@ importers: specifier: ^5.3.4 version: 5.4.10(@types/node@20.17.6)(terser@5.36.0) + examples/phoenix-liveview: + dependencies: + camelcase: + specifier: ^8.0.0 + version: 8.0.0 + devDependencies: + '@databases/pg-migrations': + specifier: ^5.0.3 + version: 5.0.3(typescript@5.7.2) + sst: + specifier: 3.3.7 + version: 3.3.7(hono@4.6.13) + typescript: + specifier: 5.7.2 + version: 5.7.2 + examples/proxy-auth: dependencies: '@electric-sql/client': @@ -741,7 +757,7 @@ importers: version: 0.3.4 tsup: specifier: ^8.0.1 - version: 8.3.5(@swc/core@1.9.1)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) + version: 8.3.5(@swc/core@1.9.1(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) tsx: specifier: ^4.19.1 version: 4.19.2 @@ -1194,7 +1210,7 @@ importers: version: 0.3.4 tsup: specifier: ^8.0.1 - version: 8.3.5(@swc/core@1.9.1)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) + version: 8.3.5(@swc/core@1.9.1(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: specifier: ^5.5.2 version: 5.6.3 @@ -1270,7 +1286,7 @@ importers: version: 0.3.4 tsup: specifier: ^8.0.1 - version: 8.3.5(@swc/core@1.9.1)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) + version: 8.3.5(@swc/core@1.9.1(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: specifier: ^5.5.2 version: 5.6.3 @@ -1330,7 +1346,7 @@ importers: version: 0.3.4 tsup: specifier: ^8.0.1 - version: 8.3.5(@swc/core@1.9.1)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) + version: 8.3.5(@swc/core@1.9.1(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: specifier: ^5.5.2 version: 5.6.3 @@ -1357,13 +1373,13 @@ importers: version: 9.15.0 vitepress: specifier: ^1.3.1 - version: 1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.6.3) + version: 1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.7.2) vitepress-plugin-tabs: specifier: ^0.5.0 - version: 0.5.0(vitepress@1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.6.3))(vue@3.5.12(typescript@5.6.3)) + version: 0.5.0(vitepress@1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.7.2))(vue@3.5.12(typescript@5.7.2)) vue-tweet: specifier: ^2.3.1 - version: 2.3.1(vue@3.5.12(typescript@5.6.3)) + version: 2.3.1(vue@3.5.12(typescript@5.7.2)) yaml: specifier: ^2.5.0 version: 2.6.0 @@ -5263,6 +5279,10 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} @@ -9355,6 +9375,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} @@ -11092,6 +11117,13 @@ snapshots: transitivePeerDependencies: - typescript + '@databases/pg-config@3.2.0(typescript@5.7.2)': + dependencies: + cosmiconfig: 8.3.6(typescript@5.7.2) + funtypes: 4.2.0 + transitivePeerDependencies: + - typescript + '@databases/pg-connection-string@1.0.0': {} '@databases/pg-data-type-id@3.0.0': {} @@ -11114,6 +11146,22 @@ snapshots: - pg-native - typescript + '@databases/pg-migrations@5.0.3(typescript@5.7.2)': + dependencies: + '@databases/migrations-base': 3.0.1 + '@databases/pg': 5.5.0(typescript@5.7.2) + '@databases/pg-config': 3.2.0(typescript@5.7.2) + assert-never: 1.3.0 + chalk: 4.1.2 + interrogator: 2.0.1 + is-interactive: 1.0.0 + parameter-reducers: 2.1.0 + semver: 7.6.3 + sucrase: 3.35.0 + transitivePeerDependencies: + - pg-native + - typescript + '@databases/pg@5.5.0(typescript@5.6.3)': dependencies: '@babel/code-frame': 7.26.2 @@ -11133,6 +11181,25 @@ snapshots: - pg-native - typescript + '@databases/pg@5.5.0(typescript@5.7.2)': + dependencies: + '@babel/code-frame': 7.26.2 + '@databases/escape-identifier': 1.0.3 + '@databases/pg-config': 3.2.0(typescript@5.7.2) + '@databases/pg-connection-string': 1.0.0 + '@databases/pg-data-type-id': 3.0.0 + '@databases/pg-errors': 1.0.0 + '@databases/push-to-async-iterable': 3.0.0 + '@databases/shared': 3.1.0 + '@databases/split-sql-query': 1.0.4(@databases/sql@3.3.0) + '@databases/sql': 3.3.0 + assert-never: 1.3.0 + pg: 8.13.1 + pg-cursor: 2.12.1(pg@8.13.1) + transitivePeerDependencies: + - pg-native + - typescript + '@databases/push-to-async-iterable@3.0.0': dependencies: '@databases/queue': 1.0.1 @@ -13996,10 +14063,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.1.4(vite@5.4.10(@types/node@20.17.6)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3))': + '@vitejs/plugin-vue@5.1.4(vite@5.4.10(@types/node@20.17.6)(terser@5.36.0))(vue@3.5.12(typescript@5.7.2))': dependencies: vite: 5.4.10(@types/node@20.17.6)(terser@5.36.0) - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) '@vitest/expect@2.1.4': dependencies: @@ -14105,29 +14172,29 @@ snapshots: '@vue/shared': 3.5.12 csstype: 3.1.3 - '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3))': + '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.7.2))': dependencies: '@vue/compiler-ssr': 3.5.12 '@vue/shared': 3.5.12 - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) '@vue/shared@3.5.12': {} - '@vueuse/core@11.2.0(vue@3.5.12(typescript@5.6.3))': + '@vueuse/core@11.2.0(vue@3.5.12(typescript@5.7.2))': dependencies: '@types/web-bluetooth': 0.0.20 '@vueuse/metadata': 11.2.0 - '@vueuse/shared': 11.2.0(vue@3.5.12(typescript@5.6.3)) - vue-demi: 0.14.10(vue@3.5.12(typescript@5.6.3)) + '@vueuse/shared': 11.2.0(vue@3.5.12(typescript@5.7.2)) + vue-demi: 0.14.10(vue@3.5.12(typescript@5.7.2)) transitivePeerDependencies: - '@vue/composition-api' - vue - '@vueuse/integrations@11.2.0(focus-trap@7.6.0)(vue@3.5.12(typescript@5.6.3))': + '@vueuse/integrations@11.2.0(focus-trap@7.6.0)(vue@3.5.12(typescript@5.7.2))': dependencies: - '@vueuse/core': 11.2.0(vue@3.5.12(typescript@5.6.3)) - '@vueuse/shared': 11.2.0(vue@3.5.12(typescript@5.6.3)) - vue-demi: 0.14.10(vue@3.5.12(typescript@5.6.3)) + '@vueuse/core': 11.2.0(vue@3.5.12(typescript@5.7.2)) + '@vueuse/shared': 11.2.0(vue@3.5.12(typescript@5.7.2)) + vue-demi: 0.14.10(vue@3.5.12(typescript@5.7.2)) optionalDependencies: focus-trap: 7.6.0 transitivePeerDependencies: @@ -14136,9 +14203,9 @@ snapshots: '@vueuse/metadata@11.2.0': {} - '@vueuse/shared@11.2.0(vue@3.5.12(typescript@5.6.3))': + '@vueuse/shared@11.2.0(vue@3.5.12(typescript@5.7.2))': dependencies: - vue-demi: 0.14.10(vue@3.5.12(typescript@5.6.3)) + vue-demi: 0.14.10(vue@3.5.12(typescript@5.7.2)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -14467,6 +14534,8 @@ snapshots: camelcase@6.3.0: {} + camelcase@8.0.0: {} + camelize@1.0.1: {} caniuse-lite@1.0.30001677: {} @@ -14699,6 +14768,15 @@ snapshots: optionalDependencies: typescript: 5.6.3 + cosmiconfig@8.3.6(typescript@5.7.2): + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.7.2 + crelt@1.0.6: {} cross-spawn@7.0.3: @@ -19081,7 +19159,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.3.5(@swc/core@1.9.1)(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0): + tsup@8.3.5(@swc/core@1.9.1(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0): dependencies: bundle-require: 5.0.0(esbuild@0.24.0) cac: 6.7.14 @@ -19175,6 +19253,8 @@ snapshots: typescript@5.6.3: {} + typescript@5.7.2: {} + uc.micro@2.1.0: {} ufo@1.5.4: {} @@ -19488,12 +19568,12 @@ snapshots: fsevents: 2.3.3 terser: 5.36.0 - vitepress-plugin-tabs@0.5.0(vitepress@1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.6.3))(vue@3.5.12(typescript@5.6.3)): + vitepress-plugin-tabs@0.5.0(vitepress@1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.7.2))(vue@3.5.12(typescript@5.7.2)): dependencies: - vitepress: 1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.6.3) - vue: 3.5.12(typescript@5.6.3) + vitepress: 1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.7.2) + vue: 3.5.12(typescript@5.7.2) - vitepress@1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.6.3): + vitepress@1.5.0(@algolia/client-search@5.13.0)(@types/node@20.17.6)(@types/react@18.3.12)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)(terser@5.36.0)(typescript@5.7.2): dependencies: '@docsearch/css': 3.7.0 '@docsearch/js': 3.7.0(@algolia/client-search@5.13.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2) @@ -19502,17 +19582,17 @@ snapshots: '@shikijs/transformers': 1.22.2 '@shikijs/types': 1.22.2 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.1.4(vite@5.4.10(@types/node@20.17.6)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3)) + '@vitejs/plugin-vue': 5.1.4(vite@5.4.10(@types/node@20.17.6)(terser@5.36.0))(vue@3.5.12(typescript@5.7.2)) '@vue/devtools-api': 7.6.3 '@vue/shared': 3.5.12 - '@vueuse/core': 11.2.0(vue@3.5.12(typescript@5.6.3)) - '@vueuse/integrations': 11.2.0(focus-trap@7.6.0)(vue@3.5.12(typescript@5.6.3)) + '@vueuse/core': 11.2.0(vue@3.5.12(typescript@5.7.2)) + '@vueuse/integrations': 11.2.0(focus-trap@7.6.0)(vue@3.5.12(typescript@5.7.2)) focus-trap: 7.6.0 mark.js: 8.11.1 minisearch: 7.1.0 shiki: 1.22.2 vite: 5.4.10(@types/node@20.17.6)(terser@5.36.0) - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) optionalDependencies: postcss: 8.4.47 transitivePeerDependencies: @@ -19579,23 +19659,23 @@ snapshots: - supports-color - terser - vue-demi@0.14.10(vue@3.5.12(typescript@5.6.3)): + vue-demi@0.14.10(vue@3.5.12(typescript@5.7.2)): dependencies: - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) - vue-tweet@2.3.1(vue@3.5.12(typescript@5.6.3)): + vue-tweet@2.3.1(vue@3.5.12(typescript@5.7.2)): dependencies: - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) - vue@3.5.12(typescript@5.6.3): + vue@3.5.12(typescript@5.7.2): dependencies: '@vue/compiler-dom': 3.5.12 '@vue/compiler-sfc': 3.5.12 '@vue/runtime-dom': 3.5.12 - '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.6.3)) + '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.7.2)) '@vue/shared': 3.5.12 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 w3c-keyname@2.2.8: {}