diff --git a/fixtures/react-router-docker/package.json b/fixtures/react-router-docker/package.json index d2eb3198793e..457abc5bcb64 100644 --- a/fixtures/react-router-docker/package.json +++ b/fixtures/react-router-docker/package.json @@ -10,10 +10,10 @@ "fixtures:build": "pnpm cli build --template docker --template .template && pnpm prettier --write ./app/ ./package.json ./tsconfig.json" }, "dependencies": { - "@react-router/dev": "^7.1.4", - "@react-router/fs-routes": "^7.1.4", - "@react-router/node": "^7.1.4", - "@react-router/serve": "^7.1.4", + "@react-router/dev": "^7.1.5", + "@react-router/fs-routes": "^7.1.5", + "@react-router/node": "^7.1.5", + "@react-router/serve": "^7.1.5", "@webstudio-is/image": "workspace:*", "@webstudio-is/react-sdk": "workspace:*", "@webstudio-is/sdk": "workspace:*", @@ -26,7 +26,7 @@ "isbot": "^5.1.22", "react": "18.3.0-canary-14898b6a9-20240318", "react-dom": "18.3.0-canary-14898b6a9-20240318", - "react-router": "^7.1.4", + "react-router": "^7.1.5", "vite": "^5.4.11", "webstudio": "workspace:*" }, diff --git a/fixtures/react-router-netlify/.gitignore b/fixtures/react-router-netlify/.gitignore new file mode 100644 index 000000000000..9b7c041f96ea --- /dev/null +++ b/fixtures/react-router-netlify/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +/node_modules/ + +# React Router +/.react-router/ +/build/ diff --git a/fixtures/react-router-netlify/.npmrc b/fixtures/react-router-netlify/.npmrc new file mode 100644 index 000000000000..43ce02f127ad --- /dev/null +++ b/fixtures/react-router-netlify/.npmrc @@ -0,0 +1,3 @@ +force=true +# to support using NODE_OPTIONS for windows tests +shell-emulator=true diff --git a/fixtures/react-router-netlify/.template/.npmrc b/fixtures/react-router-netlify/.template/.npmrc new file mode 100644 index 000000000000..43ce02f127ad --- /dev/null +++ b/fixtures/react-router-netlify/.template/.npmrc @@ -0,0 +1,3 @@ +force=true +# to support using NODE_OPTIONS for windows tests +shell-emulator=true diff --git a/fixtures/react-router-netlify/.template/package.json b/fixtures/react-router-netlify/.template/package.json new file mode 100644 index 000000000000..b21dce86c93a --- /dev/null +++ b/fixtures/react-router-netlify/.template/package.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "@webstudio-is/image": "workspace:*", + "@webstudio-is/react-sdk": "workspace:*", + "@webstudio-is/sdk": "workspace:*", + "@webstudio-is/sdk-components-animation": "workspace:*", + "@webstudio-is/sdk-components-react": "workspace:*", + "@webstudio-is/sdk-components-react-radix": "workspace:*", + "@webstudio-is/sdk-components-react-router": "workspace:*", + "webstudio": "workspace:*" + } +} diff --git a/fixtures/react-router-netlify/.template/tsconfig.json b/fixtures/react-router-netlify/.template/tsconfig.json new file mode 100644 index 000000000000..75cac78946fd --- /dev/null +++ b/fixtures/react-router-netlify/.template/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "customConditions": ["webstudio"] + } +} diff --git a/fixtures/react-router-netlify/.webstudio/config.json b/fixtures/react-router-netlify/.webstudio/config.json new file mode 100644 index 000000000000..3bad791718c2 --- /dev/null +++ b/fixtures/react-router-netlify/.webstudio/config.json @@ -0,0 +1,3 @@ +{ + "projectId": "d845c167-ea07-4875-b08d-83e97c09dcce" +} diff --git a/fixtures/react-router-netlify/.webstudio/data.json b/fixtures/react-router-netlify/.webstudio/data.json new file mode 100644 index 000000000000..7dc933cfb601 --- /dev/null +++ b/fixtures/react-router-netlify/.webstudio/data.json @@ -0,0 +1,500 @@ +{ + "build": { + "id": "f565d527-32e7-4731-bc71-aca9e9574587", + "projectId": "d845c167-ea07-4875-b08d-83e97c09dcce", + "version": 43, + "createdAt": "2025-01-04T11:01:50.091+00:00", + "updatedAt": "2025-01-04T11:01:50.091+00:00", + "pages": { + "meta": { + "siteName": "", + "faviconAssetId": "d0974db9300c1a3b0fb8b291dd9fabd45ad136478908394280af2f7087e3aecd", + "code": "" + }, + "homePage": { + "id": "9di_L14CzctvSruIoKVvE", + "name": "Home", + "title": "\"Home\"", + "rootInstanceId": "MMimeobf_zi4ZkRGXapju", + "systemDataSourceId": "2KT4-bRzToj9cAGAN_woK", + "meta": {}, + "path": "" + }, + "pages": [ + { + "id": "WPPAbLFyJD_02vhjRd8P4", + "name": "Another page", + "title": "\"Another page\"", + "history": ["/another-page"], + "rootInstanceId": "n_VBMr7klpx25buS0NV7R", + "systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl", + "meta": { + "description": "\"\"", + "excludePageFromSearch": "true", + "language": "\"\"", + "socialImageUrl": "\"\"", + "status": "200", + "redirect": "\"\"", + "documentType": "html", + "custom": [ + { + "property": "", + "content": "\"\"" + } + ] + }, + "marketplace": { + "include": false + }, + "path": "/another-page" + } + ], + "folders": [ + { + "id": "root", + "name": "Root", + "slug": "", + "children": ["9di_L14CzctvSruIoKVvE", "WPPAbLFyJD_02vhjRd8P4"] + } + ] + }, + "breakpoints": [ + [ + "rKj-wYctg3-GnqL3WHN9I", + { + "id": "rKj-wYctg3-GnqL3WHN9I", + "label": "Base" + } + ], + [ + "yH9RXhqCyeaVkrOt8MzLc", + { + "id": "yH9RXhqCyeaVkrOt8MzLc", + "label": "Tablet", + "maxWidth": 991 + } + ], + [ + "8nSCZbeS002IVwkTdoIes", + { + "id": "8nSCZbeS002IVwkTdoIes", + "label": "Mobile landscape", + "maxWidth": 767 + } + ], + [ + "7gBD25KrrbBdJYNDlhPz7", + { + "id": "7gBD25KrrbBdJYNDlhPz7", + "label": "Mobile portrait", + "maxWidth": 479 + } + ] + ], + "styles": [ + [ + "7_QL45cpvP-zG8Hkgf4cr:rKj-wYctg3-GnqL3WHN9I:display:", + { + "breakpointId": "rKj-wYctg3-GnqL3WHN9I", + "styleSourceId": "7_QL45cpvP-zG8Hkgf4cr", + "property": "display", + "value": { + "type": "keyword", + "value": "flex" + } + } + ], + [ + "7_QL45cpvP-zG8Hkgf4cr:rKj-wYctg3-GnqL3WHN9I:alignItems:", + { + "breakpointId": "rKj-wYctg3-GnqL3WHN9I", + "styleSourceId": "7_QL45cpvP-zG8Hkgf4cr", + "property": "alignItems", + "value": { + "type": "keyword", + "value": "center" + } + } + ], + [ + "7_QL45cpvP-zG8Hkgf4cr:rKj-wYctg3-GnqL3WHN9I:justifyContent:", + { + "breakpointId": "rKj-wYctg3-GnqL3WHN9I", + "styleSourceId": "7_QL45cpvP-zG8Hkgf4cr", + "property": "justifyContent", + "value": { + "type": "keyword", + "value": "center" + } + } + ], + [ + "7_QL45cpvP-zG8Hkgf4cr:rKj-wYctg3-GnqL3WHN9I:flexDirection:", + { + "breakpointId": "rKj-wYctg3-GnqL3WHN9I", + "styleSourceId": "7_QL45cpvP-zG8Hkgf4cr", + "property": "flexDirection", + "value": { + "type": "keyword", + "value": "column" + } + } + ], + [ + "0KA68BwP9gdTzE1ESO2Zp:rKj-wYctg3-GnqL3WHN9I:marginBottom:", + { + "breakpointId": "rKj-wYctg3-GnqL3WHN9I", + "styleSourceId": "0KA68BwP9gdTzE1ESO2Zp", + "property": "marginBottom", + "value": { + "type": "unit", + "unit": "em", + "value": 1 + } + } + ], + [ + "mf2C07UBmGT7y_G4Du3yg:rKj-wYctg3-GnqL3WHN9I:width:", + { + "breakpointId": "rKj-wYctg3-GnqL3WHN9I", + "styleSourceId": "mf2C07UBmGT7y_G4Du3yg", + "property": "width", + "value": { + "type": "unit", + "unit": "px", + "value": 400 + } + } + ] + ], + "styleSources": [ + [ + "7_QL45cpvP-zG8Hkgf4cr", + { + "type": "local", + "id": "7_QL45cpvP-zG8Hkgf4cr" + } + ], + [ + "0KA68BwP9gdTzE1ESO2Zp", + { + "type": "local", + "id": "0KA68BwP9gdTzE1ESO2Zp" + } + ], + [ + "mf2C07UBmGT7y_G4Du3yg", + { + "type": "local", + "id": "mf2C07UBmGT7y_G4Du3yg" + } + ] + ], + "styleSourceSelections": [ + [ + "MMimeobf_zi4ZkRGXapju", + { + "instanceId": "MMimeobf_zi4ZkRGXapju", + "values": ["7_QL45cpvP-zG8Hkgf4cr"] + } + ], + [ + "BMJfjOzunWs8XkQgvvx1e", + { + "instanceId": "BMJfjOzunWs8XkQgvvx1e", + "values": ["0KA68BwP9gdTzE1ESO2Zp"] + } + ], + [ + "uHB3Fjb7-NELG-bnH7bXB", + { + "instanceId": "uHB3Fjb7-NELG-bnH7bXB", + "values": ["mf2C07UBmGT7y_G4Du3yg"] + } + ] + ], + "props": [ + [ + "1p34InvRgqoKVqeNZ1uBb", + { + "id": "1p34InvRgqoKVqeNZ1uBb", + "instanceId": "pjkZo5EiBqaeUXBcyHf_O", + "name": "href", + "type": "page", + "value": "WPPAbLFyJD_02vhjRd8P4" + } + ], + [ + "su3ag3OxH9WTBjJg5eIyg", + { + "id": "su3ag3OxH9WTBjJg5eIyg", + "instanceId": "uHB3Fjb7-NELG-bnH7bXB", + "name": "src", + "type": "asset", + "value": "1d8bf4398f643f5333d415091507d778aaed62f28883642636cbed0be156a0ee" + } + ], + [ + "vGCYpBBB1QUPIPPIdyexn", + { + "id": "vGCYpBBB1QUPIPPIdyexn", + "instanceId": "uHB3Fjb7-NELG-bnH7bXB", + "name": "width", + "type": "asset", + "value": "1d8bf4398f643f5333d415091507d778aaed62f28883642636cbed0be156a0ee" + } + ], + [ + "JKAGY7DWpciEl0UdnWuKL", + { + "id": "JKAGY7DWpciEl0UdnWuKL", + "instanceId": "uHB3Fjb7-NELG-bnH7bXB", + "name": "height", + "type": "asset", + "value": "1d8bf4398f643f5333d415091507d778aaed62f28883642636cbed0be156a0ee" + } + ], + [ + "CAkmmL8-JAgokmeopoFXh", + { + "id": "CAkmmL8-JAgokmeopoFXh", + "instanceId": "2sIE8GxbKRBaav_zdhaZ1", + "name": "src", + "type": "string", + "value": "https://picsum.photos/id/237/100/100.jpg?blur=4&grayscale" + } + ] + ], + "dataSources": [ + [ + "2KT4-bRzToj9cAGAN_woK", + { + "type": "parameter", + "id": "2KT4-bRzToj9cAGAN_woK", + "scopeInstanceId": "MMimeobf_zi4ZkRGXapju", + "name": "system" + } + ], + [ + "tdXe9gFf83hSo9BLWU6xl", + { + "type": "parameter", + "id": "tdXe9gFf83hSo9BLWU6xl", + "scopeInstanceId": "n_VBMr7klpx25buS0NV7R", + "name": "system" + } + ] + ], + "resources": [], + "instances": [ + [ + "MMimeobf_zi4ZkRGXapju", + { + "type": "instance", + "id": "MMimeobf_zi4ZkRGXapju", + "component": "Body", + "children": [ + { + "type": "id", + "value": "MYDt0guk1-vzc7yzqyN6A" + }, + { + "type": "id", + "value": "BMJfjOzunWs8XkQgvvx1e" + }, + { + "type": "id", + "value": "pjkZo5EiBqaeUXBcyHf_O" + }, + { + "type": "id", + "value": "uHB3Fjb7-NELG-bnH7bXB" + }, + { + "type": "id", + "value": "2sIE8GxbKRBaav_zdhaZ1" + } + ] + } + ], + [ + "MYDt0guk1-vzc7yzqyN6A", + { + "type": "instance", + "id": "MYDt0guk1-vzc7yzqyN6A", + "component": "Heading", + "label": "xD", + "children": [ + { + "type": "text", + "value": "Simple Project to test CLI" + } + ] + } + ], + [ + "BMJfjOzunWs8XkQgvvx1e", + { + "type": "instance", + "id": "BMJfjOzunWs8XkQgvvx1e", + "component": "Text", + "children": [ + { + "type": "text", + "value": "Please don't change directly in the fixture" + } + ] + } + ], + [ + "pjkZo5EiBqaeUXBcyHf_O", + { + "type": "instance", + "id": "pjkZo5EiBqaeUXBcyHf_O", + "component": "Link", + "children": [ + { + "type": "text", + "value": "Test another page link" + } + ] + } + ], + [ + "n_VBMr7klpx25buS0NV7R", + { + "type": "instance", + "id": "n_VBMr7klpx25buS0NV7R", + "component": "Body", + "children": [ + { + "type": "id", + "value": "wthNByqb3RPmheb-56VYI" + } + ] + } + ], + [ + "wthNByqb3RPmheb-56VYI", + { + "type": "instance", + "id": "wthNByqb3RPmheb-56VYI", + "component": "Heading", + "children": [ + { + "type": "text", + "value": "Another page" + } + ] + } + ], + [ + "uHB3Fjb7-NELG-bnH7bXB", + { + "type": "instance", + "id": "uHB3Fjb7-NELG-bnH7bXB", + "component": "Image", + "children": [] + } + ], + [ + "2sIE8GxbKRBaav_zdhaZ1", + { + "type": "instance", + "id": "2sIE8GxbKRBaav_zdhaZ1", + "component": "Image", + "children": [] + } + ] + ], + "deployment": { + "destination": "saas", + "domains": ["cli-basic-test-d0osr"], + "assetsDomain": "cli-basic-test-d0osr", + "excludeWstdDomainFromSearch": false + } + }, + "page": { + "id": "9di_L14CzctvSruIoKVvE", + "name": "Home", + "title": "\"Home\"", + "rootInstanceId": "MMimeobf_zi4ZkRGXapju", + "systemDataSourceId": "2KT4-bRzToj9cAGAN_woK", + "meta": {}, + "path": "" + }, + "pages": [ + { + "id": "9di_L14CzctvSruIoKVvE", + "name": "Home", + "title": "\"Home\"", + "rootInstanceId": "MMimeobf_zi4ZkRGXapju", + "systemDataSourceId": "2KT4-bRzToj9cAGAN_woK", + "meta": {}, + "path": "" + }, + { + "id": "WPPAbLFyJD_02vhjRd8P4", + "name": "Another page", + "title": "\"Another page\"", + "history": ["/another-page"], + "rootInstanceId": "n_VBMr7klpx25buS0NV7R", + "systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl", + "meta": { + "description": "\"\"", + "excludePageFromSearch": "true", + "language": "\"\"", + "socialImageUrl": "\"\"", + "status": "200", + "redirect": "\"\"", + "documentType": "html", + "custom": [ + { + "property": "", + "content": "\"\"" + } + ] + }, + "marketplace": { + "include": false + }, + "path": "/another-page" + } + ], + "assets": [ + { + "id": "1d8bf4398f643f5333d415091507d778aaed62f28883642636cbed0be156a0ee", + "name": "iconly_svg_converted-converted_zMaMiAAutUl8XrITgz7d1.svg", + "description": null, + "projectId": "d845c167-ea07-4875-b08d-83e97c09dcce", + "size": 999, + "type": "image", + "format": "svg", + "createdAt": "2024-07-26T13:39:48.678+00:00", + "meta": { + "width": 14, + "height": 16 + } + }, + { + "id": "d0974db9300c1a3b0fb8b291dd9fabd45ad136478908394280af2f7087e3aecd", + "name": "147-1478573_cat-icon-png-black-cat-png-icon.png_ZJ6-qJjk1RlFzuYwyCXdp.jpeg", + "description": null, + "projectId": "d845c167-ea07-4875-b08d-83e97c09dcce", + "size": 64701, + "type": "image", + "format": "jpg", + "createdAt": "2024-12-06T14:36:07.046+00:00", + "meta": { + "width": 820, + "height": 985 + } + } + ], + "user": { + "email": "hello@webstudio.is" + }, + "projectDomain": "cli-basic-test-d0osr", + "projectTitle": "cli-basic-test", + "origin": "https://main.development.webstudio.is" +} diff --git a/fixtures/react-router-netlify/app/__generated__/$resources.sitemap.xml.ts b/fixtures/react-router-netlify/app/__generated__/$resources.sitemap.xml.ts new file mode 100644 index 000000000000..181eac4621f3 --- /dev/null +++ b/fixtures/react-router-netlify/app/__generated__/$resources.sitemap.xml.ts @@ -0,0 +1,6 @@ +export const sitemap = [ + { + path: "/", + lastModified: "2025-01-04", + }, +]; diff --git a/fixtures/react-router-netlify/app/__generated__/[another-page]._index.server.tsx b/fixtures/react-router-netlify/app/__generated__/[another-page]._index.server.tsx new file mode 100644 index 000000000000..dc6510942c47 --- /dev/null +++ b/fixtures/react-router-netlify/app/__generated__/[another-page]._index.server.tsx @@ -0,0 +1,39 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import type { PageMeta } from "@webstudio-is/sdk"; +import type { System, ResourceRequest } from "@webstudio-is/sdk"; +export const getResources = (_props: { system: System }) => { + const _data = new Map([]); + const _action = new Map([]); + return { data: _data, action: _action }; +}; + +export const getPageMeta = ({ + system, + resources, +}: { + system: System; + resources: Record; +}): PageMeta => { + return { + title: "Another page", + description: "", + excludePageFromSearch: true, + language: "", + socialImageAssetName: undefined, + socialImageUrl: "", + status: 200, + redirect: "", + custom: [], + }; +}; + +type Params = Record; +export const getRemixParams = ({ ...params }: Params): Params => { + return params; +}; + +export const projectId = "d845c167-ea07-4875-b08d-83e97c09dcce"; + +export const contactEmail = "hello@webstudio.is"; diff --git a/fixtures/react-router-netlify/app/__generated__/[another-page]._index.tsx b/fixtures/react-router-netlify/app/__generated__/[another-page]._index.tsx new file mode 100644 index 000000000000..0f11ceedef83 --- /dev/null +++ b/fixtures/react-router-netlify/app/__generated__/[another-page]._index.tsx @@ -0,0 +1,37 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import { Fragment, useState } from "react"; +import type { FontAsset, ImageAsset } from "@webstudio-is/sdk"; +import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime"; +import { Body as Body } from "@webstudio-is/sdk-components-react-router"; +import { Heading as Heading } from "@webstudio-is/sdk-components-react"; + +export const siteName = ""; + +export const favIconAsset: ImageAsset | undefined = { + id: "d0974db9300c1a3b0fb8b291dd9fabd45ad136478908394280af2f7087e3aecd", + name: "147-1478573_cat-icon-png-black-cat-png-icon.png_ZJ6-qJjk1RlFzuYwyCXdp.jpeg", + description: null, + projectId: "d845c167-ea07-4875-b08d-83e97c09dcce", + size: 64701, + type: "image", + format: "jpg", + createdAt: "2024-12-06T14:36:07.046+00:00", + meta: { width: 820, height: 985 }, +}; + +// Font assets on current page (can be preloaded) +export const pageFontAssets: FontAsset[] = []; + +export const pageBackgroundImageAssets: ImageAsset[] = []; + +const Page = ({}: { system: any }) => { + return ( + + {"Another page"} + + ); +}; + +export { Page }; diff --git a/fixtures/react-router-netlify/app/__generated__/_index.server.tsx b/fixtures/react-router-netlify/app/__generated__/_index.server.tsx new file mode 100644 index 000000000000..83d760a977b9 --- /dev/null +++ b/fixtures/react-router-netlify/app/__generated__/_index.server.tsx @@ -0,0 +1,39 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import type { PageMeta } from "@webstudio-is/sdk"; +import type { System, ResourceRequest } from "@webstudio-is/sdk"; +export const getResources = (_props: { system: System }) => { + const _data = new Map([]); + const _action = new Map([]); + return { data: _data, action: _action }; +}; + +export const getPageMeta = ({ + system, + resources, +}: { + system: System; + resources: Record; +}): PageMeta => { + return { + title: "Home", + description: undefined, + excludePageFromSearch: undefined, + language: undefined, + socialImageAssetName: undefined, + socialImageUrl: undefined, + status: undefined, + redirect: undefined, + custom: [], + }; +}; + +type Params = Record; +export const getRemixParams = ({ ...params }: Params): Params => { + return params; +}; + +export const projectId = "d845c167-ea07-4875-b08d-83e97c09dcce"; + +export const contactEmail = "hello@webstudio.is"; diff --git a/fixtures/react-router-netlify/app/__generated__/_index.tsx b/fixtures/react-router-netlify/app/__generated__/_index.tsx new file mode 100644 index 000000000000..9c073984cf91 --- /dev/null +++ b/fixtures/react-router-netlify/app/__generated__/_index.tsx @@ -0,0 +1,64 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import { Fragment, useState } from "react"; +import type { FontAsset, ImageAsset } from "@webstudio-is/sdk"; +import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime"; +import { + Body as Body, + Link as Link, +} from "@webstudio-is/sdk-components-react-router"; +import { + Heading as Heading, + Text as Text, + Image as Image, +} from "@webstudio-is/sdk-components-react"; + +export const siteName = ""; + +export const favIconAsset: ImageAsset | undefined = { + id: "d0974db9300c1a3b0fb8b291dd9fabd45ad136478908394280af2f7087e3aecd", + name: "147-1478573_cat-icon-png-black-cat-png-icon.png_ZJ6-qJjk1RlFzuYwyCXdp.jpeg", + description: null, + projectId: "d845c167-ea07-4875-b08d-83e97c09dcce", + size: 64701, + type: "image", + format: "jpg", + createdAt: "2024-12-06T14:36:07.046+00:00", + meta: { width: 820, height: 985 }, +}; + +// Font assets on current page (can be preloaded) +export const pageFontAssets: FontAsset[] = []; + +export const pageBackgroundImageAssets: ImageAsset[] = []; + +export const CustomCode = () => { + return <>; +}; + +const Page = ({}: { system: any }) => { + return ( + + {"Simple Project to test CLI"} + + {"Please don't change directly in the fixture"} + + + {"Test another page link"} + + + + + ); +}; + +export { Page }; diff --git a/fixtures/react-router-netlify/app/__generated__/index.css b/fixtures/react-router-netlify/app/__generated__/index.css new file mode 100644 index 000000000000..dd85fdf13b99 --- /dev/null +++ b/fixtures/react-router-netlify/app/__generated__/index.css @@ -0,0 +1,118 @@ +@media all { + :root { + display: grid; + min-height: 100%; + font-family: Arial, Roboto, sans-serif; + font-size: 16px; + line-height: 1.2; + white-space: pre-wrap; + white-space-collapse: preserve; + } + :where(body.w-body) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + margin: 0; + } + :where(h1.w-heading) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + } + :where(h2.w-heading) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + } + :where(h3.w-heading) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + } + :where(h4.w-heading) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + } + :where(h5.w-heading) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + } + :where(h6.w-heading) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + } + :where(div.w-text) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + min-height: 1em; + } + :where(a.w-link) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + display: inline-block; + } + :where(img.w-image) { + box-sizing: border-box; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + outline-width: 1px; + max-width: 100%; + display: block; + height: auto; + } +} +@media all { + .c1jaw2zx { + display: flex; + } + .cbipm55 { + align-items: center; + } + .ctniqj4 { + justify-content: center; + } + .ctgx88l { + flex-direction: column; + } + .cn3rfux { + margin-bottom: 1em; + } + .c161qeci { + width: 400px; + } +} diff --git a/fixtures/react-router-netlify/app/constants.mjs b/fixtures/react-router-netlify/app/constants.mjs new file mode 100644 index 000000000000..574f3652b794 --- /dev/null +++ b/fixtures/react-router-netlify/app/constants.mjs @@ -0,0 +1,29 @@ +/** + * We use mjs extension as constants in this file is shared with the build script + * and we use `node --eval` to extract the constants. + */ +export const assetBaseUrl = "/assets/"; + +/** + * @type {import("@webstudio-is/image").ImageLoader} + */ +export const imageLoader = (props) => { + if (import.meta.env.DEV) { + return props.src; + } + + if (props.format === "raw") { + return props.src; + } + + // https://docs.netlify.com/image-cdn/overview/ + const searchParams = new URLSearchParams(); + searchParams.set("url", props.src); + searchParams.set("w", props.width.toString()); + if (props.height) { + searchParams.set("h", props.height.toString()); + } + searchParams.set("q", props.quality.toString()); + // fit=contain by default + return `/.netlify/images?${searchParams}`; +}; diff --git a/fixtures/react-router-netlify/app/extension.ts b/fixtures/react-router-netlify/app/extension.ts new file mode 100644 index 000000000000..bffd05d48e17 --- /dev/null +++ b/fixtures/react-router-netlify/app/extension.ts @@ -0,0 +1,13 @@ +import { ResourceRequest } from "@webstudio-is/sdk"; + +declare module "react-router" { + interface AppLoadContext { + EXCLUDE_FROM_SEARCH: boolean; + getDefaultActionResource?: (options: { + url: URL; + projectId: string; + contactEmail: string; + formData: FormData; + }) => ResourceRequest; + } +} diff --git a/fixtures/react-router-netlify/app/root.tsx b/fixtures/react-router-netlify/app/root.tsx new file mode 100644 index 000000000000..aa2a8c416496 --- /dev/null +++ b/fixtures/react-router-netlify/app/root.tsx @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ + +import { Links, Meta, Outlet, useMatches } from "react-router"; +// @todo think about how to make __generated__ typeable +// @ts-ignore +import { CustomCode } from "./__generated__/_index"; + +const Root = () => { + // Get language from matches + const matches = useMatches(); + + const lastMatchWithLanguage = matches.findLast((match) => { + // @ts-ignore + const language = match?.data?.pageMeta?.language; + return language != null; + }); + + // @ts-ignore + const lang = lastMatchWithLanguage?.data?.pageMeta?.language ?? "en"; + + return ( + + + + + + + + + + + ); +}; + +export default Root; diff --git a/fixtures/react-router-netlify/app/routes.ts b/fixtures/react-router-netlify/app/routes.ts new file mode 100644 index 000000000000..4c05936cb638 --- /dev/null +++ b/fixtures/react-router-netlify/app/routes.ts @@ -0,0 +1,4 @@ +import { type RouteConfig } from "@react-router/dev/routes"; +import { flatRoutes } from "@react-router/fs-routes"; + +export default flatRoutes() satisfies RouteConfig; diff --git a/fixtures/react-router-netlify/app/routes/[another-page]._index.tsx b/fixtures/react-router-netlify/app/routes/[another-page]._index.tsx new file mode 100644 index 000000000000..e9d062f92306 --- /dev/null +++ b/fixtures/react-router-netlify/app/routes/[another-page]._index.tsx @@ -0,0 +1,295 @@ +import { + type MetaFunction, + type LinksFunction, + type LinkDescriptor, + type ActionFunctionArgs, + type LoaderFunctionArgs, + type HeadersFunction, + data, + redirect, + useLoaderData, +} from "react-router"; +import { + isLocalResource, + loadResource, + loadResources, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/sdk/runtime"; +import { + ReactSdkContext, + PageSettingsMeta, + PageSettingsTitle, +} from "@webstudio-is/react-sdk/runtime"; +import { + Page, + siteName, + favIconAsset, + pageFontAssets, + pageBackgroundImageAssets, +} from "../__generated__/[another-page]._index"; +import { + getResources, + getPageMeta, + getRemixParams, + projectId, + contactEmail, +} from "../__generated__/[another-page]._index.server"; +import { assetBaseUrl, imageLoader } from "../constants.mjs"; +import css from "../__generated__/index.css?url"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; + +const customFetch: typeof fetch = (input, init) => { + if (typeof input !== "string") { + return fetch(input, init); + } + + if (isLocalResource(input, "sitemap.xml")) { + // @todo: dynamic import sitemap ??? + const response = new Response(JSON.stringify(sitemap)); + response.headers.set("content-type", "application/json; charset=utf-8"); + return Promise.resolve(response); + } + + return fetch(input, init); +}; + +export const loader = async (arg: LoaderFunctionArgs) => { + const url = new URL(arg.request.url); + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + url.host = host; + url.protocol = "https"; + + const params = getRemixParams(arg.params); + const system = { + params, + search: Object.fromEntries(url.searchParams), + origin: url.origin, + }; + + const resources = await loadResources( + customFetch, + getResources({ system }).data + ); + const pageMeta = getPageMeta({ system, resources }); + + if (pageMeta.redirect) { + const status = + pageMeta.status === 301 || pageMeta.status === 302 + ? pageMeta.status + : 302; + throw redirect(pageMeta.redirect, status); + } + + // typecheck + arg.context.EXCLUDE_FROM_SEARCH satisfies boolean; + + if (arg.context.EXCLUDE_FROM_SEARCH) { + pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH; + } + + return data( + { + host, + url: url.href, + system, + resources, + pageMeta, + }, + // No way for current information to change, so add cache for 10 minutes + // In case of CRM Data, this should be set to 0 + { + status: pageMeta.status, + headers: { + "Cache-Control": "public, max-age=600", + }, + } + ); +}; + +export const headers: HeadersFunction = () => { + return { + "Cache-Control": "public, max-age=0, must-revalidate", + }; +}; + +export const meta: MetaFunction = ({ data }) => { + const metas: ReturnType = []; + if (data === undefined) { + return metas; + } + + const origin = `https://${data.host}`; + + if (siteName) { + metas.push({ + "script:ld+json": { + "@context": "https://schema.org", + "@type": "WebSite", + name: siteName, + url: origin, + }, + }); + } + + return metas; +}; + +export const links: LinksFunction = () => { + const result: LinkDescriptor[] = []; + + result.push({ + rel: "stylesheet", + href: css, + }); + + if (favIconAsset) { + result.push({ + rel: "icon", + href: imageLoader({ + src: `${assetBaseUrl}${favIconAsset.name}`, + // width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search + width: 144, + height: 144, + fit: "pad", + quality: 100, + format: "auto", + }), + type: undefined, + }); + } + + for (const asset of pageFontAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${asset.name}`, + as: "font", + crossOrigin: "anonymous", + }); + } + + for (const backgroundImageAsset of pageBackgroundImageAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${backgroundImageAsset.name}`, + as: "image", + }); + } + + return result; +}; + +const getRequestHost = (request: Request): string => + request.headers.get("x-forwarded-host") || request.headers.get("host") || ""; + +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { + try { + const url = new URL(request.url); + url.host = getRequestHost(request); + + const formData = await request.formData(); + + const system = { + params: {}, + search: {}, + origin: url.origin, + }; + + const resourceName = formData.get(formIdFieldName); + let resource = + typeof resourceName === "string" + ? getResources({ system }).action.get(resourceName) + : undefined; + + const formBotValue = formData.get(formBotFieldName); + + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } + + const submitTime = parseInt(formBotValue, 16); + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + formData.delete(formIdFieldName); + formData.delete(formBotFieldName); + + if (resource) { + resource.headers.push({ + name: "Content-Type", + value: "application/json", + }); + resource.body = Object.fromEntries(formData); + } else { + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + resource = context.getDefaultActionResource?.({ + url, + projectId, + contactEmail, + formData, + }); + } + + if (resource === undefined) { + throw Error("Resource not found"); + } + const { ok, statusText } = await loadResource(fetch, resource); + if (ok) { + return { success: true }; + } + return { success: false, errors: [statusText] }; + } catch (error) { + console.error(error); + + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; + } +}; + +const Outlet = () => { + const { system, resources, url, pageMeta, host } = + useLoaderData(); + return ( + + {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */} + + + {pageMeta.title} + + ); +}; + +export default Outlet; diff --git a/fixtures/react-router-netlify/app/routes/[robots.txt].tsx b/fixtures/react-router-netlify/app/routes/[robots.txt].tsx new file mode 100644 index 000000000000..cb8fe07b8f6f --- /dev/null +++ b/fixtures/react-router-netlify/app/routes/[robots.txt].tsx @@ -0,0 +1,24 @@ +import type { LoaderFunctionArgs } from "react-router"; + +export const loader = (arg: LoaderFunctionArgs) => { + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + + return new Response( + ` +User-agent: * +Disallow: /api/ + +Sitemap: https://${host}/sitemap.xml + + `, + { + headers: { + "Content-Type": "text/plain", + }, + status: 200, + } + ); +}; diff --git a/fixtures/react-router-netlify/app/routes/[sitemap.xml]._index.tsx b/fixtures/react-router-netlify/app/routes/[sitemap.xml]._index.tsx new file mode 100644 index 000000000000..8362eebee742 --- /dev/null +++ b/fixtures/react-router-netlify/app/routes/[sitemap.xml]._index.tsx @@ -0,0 +1,34 @@ +import type { LoaderFunctionArgs } from "react-router"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; + +export const loader = (arg: LoaderFunctionArgs) => { + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + + const urls = sitemap.map((page) => { + const url = new URL(`https://${host}${page.path}`); + + return ` + + ${url.href} + ${page.lastModified.split("T")[0]} + + `; + }); + + return new Response( + ` + +${urls.join("")} + + `, + { + headers: { + "Content-Type": "application/xml", + }, + status: 200, + } + ); +}; diff --git a/fixtures/react-router-netlify/app/routes/_index.tsx b/fixtures/react-router-netlify/app/routes/_index.tsx new file mode 100644 index 000000000000..5ecd622ce95e --- /dev/null +++ b/fixtures/react-router-netlify/app/routes/_index.tsx @@ -0,0 +1,295 @@ +import { + type MetaFunction, + type LinksFunction, + type LinkDescriptor, + type ActionFunctionArgs, + type LoaderFunctionArgs, + type HeadersFunction, + data, + redirect, + useLoaderData, +} from "react-router"; +import { + isLocalResource, + loadResource, + loadResources, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/sdk/runtime"; +import { + ReactSdkContext, + PageSettingsMeta, + PageSettingsTitle, +} from "@webstudio-is/react-sdk/runtime"; +import { + Page, + siteName, + favIconAsset, + pageFontAssets, + pageBackgroundImageAssets, +} from "../__generated__/_index"; +import { + getResources, + getPageMeta, + getRemixParams, + projectId, + contactEmail, +} from "../__generated__/_index.server"; +import { assetBaseUrl, imageLoader } from "../constants.mjs"; +import css from "../__generated__/index.css?url"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; + +const customFetch: typeof fetch = (input, init) => { + if (typeof input !== "string") { + return fetch(input, init); + } + + if (isLocalResource(input, "sitemap.xml")) { + // @todo: dynamic import sitemap ??? + const response = new Response(JSON.stringify(sitemap)); + response.headers.set("content-type", "application/json; charset=utf-8"); + return Promise.resolve(response); + } + + return fetch(input, init); +}; + +export const loader = async (arg: LoaderFunctionArgs) => { + const url = new URL(arg.request.url); + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + url.host = host; + url.protocol = "https"; + + const params = getRemixParams(arg.params); + const system = { + params, + search: Object.fromEntries(url.searchParams), + origin: url.origin, + }; + + const resources = await loadResources( + customFetch, + getResources({ system }).data + ); + const pageMeta = getPageMeta({ system, resources }); + + if (pageMeta.redirect) { + const status = + pageMeta.status === 301 || pageMeta.status === 302 + ? pageMeta.status + : 302; + throw redirect(pageMeta.redirect, status); + } + + // typecheck + arg.context.EXCLUDE_FROM_SEARCH satisfies boolean; + + if (arg.context.EXCLUDE_FROM_SEARCH) { + pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH; + } + + return data( + { + host, + url: url.href, + system, + resources, + pageMeta, + }, + // No way for current information to change, so add cache for 10 minutes + // In case of CRM Data, this should be set to 0 + { + status: pageMeta.status, + headers: { + "Cache-Control": "public, max-age=600", + }, + } + ); +}; + +export const headers: HeadersFunction = () => { + return { + "Cache-Control": "public, max-age=0, must-revalidate", + }; +}; + +export const meta: MetaFunction = ({ data }) => { + const metas: ReturnType = []; + if (data === undefined) { + return metas; + } + + const origin = `https://${data.host}`; + + if (siteName) { + metas.push({ + "script:ld+json": { + "@context": "https://schema.org", + "@type": "WebSite", + name: siteName, + url: origin, + }, + }); + } + + return metas; +}; + +export const links: LinksFunction = () => { + const result: LinkDescriptor[] = []; + + result.push({ + rel: "stylesheet", + href: css, + }); + + if (favIconAsset) { + result.push({ + rel: "icon", + href: imageLoader({ + src: `${assetBaseUrl}${favIconAsset.name}`, + // width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search + width: 144, + height: 144, + fit: "pad", + quality: 100, + format: "auto", + }), + type: undefined, + }); + } + + for (const asset of pageFontAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${asset.name}`, + as: "font", + crossOrigin: "anonymous", + }); + } + + for (const backgroundImageAsset of pageBackgroundImageAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${backgroundImageAsset.name}`, + as: "image", + }); + } + + return result; +}; + +const getRequestHost = (request: Request): string => + request.headers.get("x-forwarded-host") || request.headers.get("host") || ""; + +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { + try { + const url = new URL(request.url); + url.host = getRequestHost(request); + + const formData = await request.formData(); + + const system = { + params: {}, + search: {}, + origin: url.origin, + }; + + const resourceName = formData.get(formIdFieldName); + let resource = + typeof resourceName === "string" + ? getResources({ system }).action.get(resourceName) + : undefined; + + const formBotValue = formData.get(formBotFieldName); + + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } + + const submitTime = parseInt(formBotValue, 16); + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + formData.delete(formIdFieldName); + formData.delete(formBotFieldName); + + if (resource) { + resource.headers.push({ + name: "Content-Type", + value: "application/json", + }); + resource.body = Object.fromEntries(formData); + } else { + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + resource = context.getDefaultActionResource?.({ + url, + projectId, + contactEmail, + formData, + }); + } + + if (resource === undefined) { + throw Error("Resource not found"); + } + const { ok, statusText } = await loadResource(fetch, resource); + if (ok) { + return { success: true }; + } + return { success: false, errors: [statusText] }; + } catch (error) { + console.error(error); + + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; + } +}; + +const Outlet = () => { + const { system, resources, url, pageMeta, host } = + useLoaderData(); + return ( + + {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */} + + + {pageMeta.title} + + ); +}; + +export default Outlet; diff --git a/fixtures/react-router-netlify/netlify.toml b/fixtures/react-router-netlify/netlify.toml new file mode 100644 index 000000000000..3b400c4fe7ec --- /dev/null +++ b/fixtures/react-router-netlify/netlify.toml @@ -0,0 +1,6 @@ +[build] +command = "react-router build" +publish = "build/client" + +[dev] +command = "react-router dev" diff --git a/fixtures/react-router-netlify/package.json b/fixtures/react-router-netlify/package.json new file mode 100644 index 000000000000..d0f941990a64 --- /dev/null +++ b/fixtures/react-router-netlify/package.json @@ -0,0 +1,44 @@ +{ + "name": "webstudio-react-router-netlify", + "scripts": { + "typecheck": "tsc", + "cli": "NODE_OPTIONS='--conditions=webstudio --import=tsx' webstudio", + "fixtures:link": "pnpm cli link --link https://p-d845c167-ea07-4875-b08d-83e97c09dcce-dot-${BUILDER_HOST:-main.development.webstudio.is}'?authToken=e9d1343f-9298-4fd3-a66e-f89a5af2dd93'", + "fixtures:sync": "pnpm cli sync --buildId f565d527-32e7-4731-bc71-aca9e9574587 && pnpm prettier --write ./.webstudio/", + "fixtures:build": "pnpm cli build --template netlify --template .template && pnpm prettier --write ./app/ ./package.json ./tsconfig.json", + "build": "react-router build", + "dev": "react-router dev", + "start": "npx netlify-cli serve", + "deploy": "npx netlify-cli deploy --build --prod" + }, + "dependencies": { + "@netlify/vite-plugin-react-router": "^1.0.0", + "@react-router/dev": "^7.1.5", + "@react-router/fs-routes": "^7.1.5", + "@react-router/node": "^7.1.5", + "@webstudio-is/image": "workspace:*", + "@webstudio-is/react-sdk": "workspace:*", + "@webstudio-is/sdk": "workspace:*", + "@webstudio-is/sdk-components-animation": "workspace:*", + "@webstudio-is/sdk-components-react": "workspace:*", + "@webstudio-is/sdk-components-react-radix": "workspace:*", + "@webstudio-is/sdk-components-react-router": "workspace:*", + "isbot": "^5.1.22", + "react": "18.3.0-canary-14898b6a9-20240318", + "react-dom": "18.3.0-canary-14898b6a9-20240318", + "react-router": "^7.1.5", + "vite": "^5.4.11", + "webstudio": "workspace:*" + }, + "type": "module", + "private": true, + "sideEffects": false, + "devDependencies": { + "@types/react": "^18.2.70", + "@types/react-dom": "^18.2.25", + "typescript": "5.7.3" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/fixtures/react-router-netlify/public/assets/147-1478573_cat-icon-png-black-cat-png-icon.png_ZJ6-qJjk1RlFzuYwyCXdp.jpeg b/fixtures/react-router-netlify/public/assets/147-1478573_cat-icon-png-black-cat-png-icon.png_ZJ6-qJjk1RlFzuYwyCXdp.jpeg new file mode 100644 index 000000000000..841650f92a91 Binary files /dev/null and b/fixtures/react-router-netlify/public/assets/147-1478573_cat-icon-png-black-cat-png-icon.png_ZJ6-qJjk1RlFzuYwyCXdp.jpeg differ diff --git a/fixtures/react-router-netlify/public/assets/iconly_svg_converted-converted_zMaMiAAutUl8XrITgz7d1.svg b/fixtures/react-router-netlify/public/assets/iconly_svg_converted-converted_zMaMiAAutUl8XrITgz7d1.svg new file mode 100644 index 000000000000..bc70d3a453d8 --- /dev/null +++ b/fixtures/react-router-netlify/public/assets/iconly_svg_converted-converted_zMaMiAAutUl8XrITgz7d1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fixtures/react-router-netlify/public/favicon.ico b/fixtures/react-router-netlify/public/favicon.ico new file mode 100644 index 000000000000..9cb8c815dc0a Binary files /dev/null and b/fixtures/react-router-netlify/public/favicon.ico differ diff --git a/fixtures/react-router-netlify/tsconfig.json b/fixtures/react-router-netlify/tsconfig.json new file mode 100644 index 000000000000..0744b14c56a2 --- /dev/null +++ b/fixtures/react-router-netlify/tsconfig.json @@ -0,0 +1,19 @@ +{ + "include": ["**/*.ts", "**/*.tsx", "**/*.mjs"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "types": ["vite/client", "@webstudio-is/react-sdk/placeholder"], + "isolatedModules": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2022", + "strict": true, + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "skipLibCheck": true, + "customConditions": ["webstudio"] + } +} diff --git a/fixtures/react-router-netlify/vite.config.ts b/fixtures/react-router-netlify/vite.config.ts new file mode 100644 index 000000000000..9d35a0ebb8ce --- /dev/null +++ b/fixtures/react-router-netlify/vite.config.ts @@ -0,0 +1,7 @@ +import { reactRouter } from "@react-router/dev/vite"; +import { defineConfig } from "vite"; +import netlifyPlugin from "@netlify/vite-plugin-react-router"; + +export default defineConfig({ + plugins: [reactRouter(), netlifyPlugin()], +}); diff --git a/packages/cli/package.json b/packages/cli/package.json index bd80b66c8adb..27413f9b484a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -50,8 +50,9 @@ "devDependencies": { "@netlify/remix-adapter": "^2.5.1", "@netlify/remix-edge-adapter": "3.4.2", - "@react-router/dev": "^7.1.4", - "@react-router/fs-routes": "^7.1.4", + "@netlify/vite-plugin-react-router": "^1.0.0", + "@react-router/dev": "^7.1.5", + "@react-router/fs-routes": "^7.1.5", "@remix-run/cloudflare": "^2.15.2", "@remix-run/cloudflare-pages": "^2.15.2", "@remix-run/dev": "^2.15.2", @@ -77,7 +78,7 @@ "prettier": "3.4.2", "react": "18.3.0-canary-14898b6a9-20240318", "react-dom": "18.3.0-canary-14898b6a9-20240318", - "react-router": "^7.1.4", + "react-router": "^7.1.5", "ts-expect": "^1.3.0", "vike": "^0.4.220", "vite": "^5.4.11", diff --git a/packages/cli/src/commands/init-flow.ts b/packages/cli/src/commands/init-flow.ts index 4e49634067d2..407a976dff80 100644 --- a/packages/cli/src/commands/init-flow.ts +++ b/packages/cli/src/commands/init-flow.ts @@ -136,6 +136,7 @@ const getDeploymentInstructions = ( switch (deployTarget) { case "vercel": return `Run ${pc.dim("npx vercel")} to publish on Vercel.`; + case "netlify": case "netlify-functions": case "netlify-edge-functions": return [ diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts index 44c9c01e7492..693197d94eaa 100644 --- a/packages/cli/src/config.ts +++ b/packages/cli/src/config.ts @@ -68,6 +68,11 @@ export const PROJECT_TEMPLATES = [ label: "Vercel", expand: ["defaults", "vercel"], }, + { + value: "netlify" as const, + label: "Netlify", + expand: ["react-router", "react-router-netlify"], + }, { value: "netlify-functions" as const, label: "Netlify Functions", diff --git a/packages/cli/src/prebuild.ts b/packages/cli/src/prebuild.ts index d73ffcb6f241..3299770c41ab 100644 --- a/packages/cli/src/prebuild.ts +++ b/packages/cli/src/prebuild.ts @@ -445,7 +445,7 @@ export const prebuild = async (options: { assetsToDownload.push( limit(() => downloadAsset( - `${assetOrigin}/cgi/image/${asset.name}?format=auto`, + `${assetOrigin}/cgi/image/${asset.name}?format=raw`, asset.name, assetBaseUrl ) diff --git a/packages/cli/templates/react-router-docker/package.json b/packages/cli/templates/react-router-docker/package.json index d017ef62b3e7..b27445da844d 100644 --- a/packages/cli/templates/react-router-docker/package.json +++ b/packages/cli/templates/react-router-docker/package.json @@ -1,6 +1,10 @@ { + "scripts": { + "start": "react-router-serve ./build/server/index.js" + }, "dependencies": { - "@react-router/node": "^7.1.4", + "@react-router/node": "^7.1.5", + "@react-router/serve": "^7.1.5", "h3": "^1.14.0", "ipx": "^3.0.1" } diff --git a/packages/cli/templates/react-router-netlify/app/constants.mjs b/packages/cli/templates/react-router-netlify/app/constants.mjs new file mode 100644 index 000000000000..574f3652b794 --- /dev/null +++ b/packages/cli/templates/react-router-netlify/app/constants.mjs @@ -0,0 +1,29 @@ +/** + * We use mjs extension as constants in this file is shared with the build script + * and we use `node --eval` to extract the constants. + */ +export const assetBaseUrl = "/assets/"; + +/** + * @type {import("@webstudio-is/image").ImageLoader} + */ +export const imageLoader = (props) => { + if (import.meta.env.DEV) { + return props.src; + } + + if (props.format === "raw") { + return props.src; + } + + // https://docs.netlify.com/image-cdn/overview/ + const searchParams = new URLSearchParams(); + searchParams.set("url", props.src); + searchParams.set("w", props.width.toString()); + if (props.height) { + searchParams.set("h", props.height.toString()); + } + searchParams.set("q", props.quality.toString()); + // fit=contain by default + return `/.netlify/images?${searchParams}`; +}; diff --git a/packages/cli/templates/react-router-netlify/netlify.toml b/packages/cli/templates/react-router-netlify/netlify.toml new file mode 100644 index 000000000000..3b400c4fe7ec --- /dev/null +++ b/packages/cli/templates/react-router-netlify/netlify.toml @@ -0,0 +1,6 @@ +[build] +command = "react-router build" +publish = "build/client" + +[dev] +command = "react-router dev" diff --git a/packages/cli/templates/react-router-netlify/package.json b/packages/cli/templates/react-router-netlify/package.json new file mode 100644 index 000000000000..a6227194f1fa --- /dev/null +++ b/packages/cli/templates/react-router-netlify/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "start": "npx netlify-cli serve", + "deploy": "npx netlify-cli deploy --build --prod" + }, + "dependencies": { + "@netlify/vite-plugin-react-router": "^1.0.0", + "@react-router/node": "^7.1.5" + } +} diff --git a/packages/cli/templates/react-router-netlify/vite.config.ts b/packages/cli/templates/react-router-netlify/vite.config.ts new file mode 100644 index 000000000000..9d35a0ebb8ce --- /dev/null +++ b/packages/cli/templates/react-router-netlify/vite.config.ts @@ -0,0 +1,7 @@ +import { reactRouter } from "@react-router/dev/vite"; +import { defineConfig } from "vite"; +import netlifyPlugin from "@netlify/vite-plugin-react-router"; + +export default defineConfig({ + plugins: [reactRouter(), netlifyPlugin()], +}); diff --git a/packages/cli/templates/react-router/package.json b/packages/cli/templates/react-router/package.json index 93bdc5bfaa81..338d17e643c4 100644 --- a/packages/cli/templates/react-router/package.json +++ b/packages/cli/templates/react-router/package.json @@ -5,13 +5,11 @@ "scripts": { "build": "react-router build", "dev": "react-router dev", - "start": "react-router-serve ./build/server/index.js", "typecheck": "tsc" }, "dependencies": { - "@react-router/dev": "^7.1.4", - "@react-router/fs-routes": "^7.1.4", - "@react-router/serve": "^7.1.4", + "@react-router/dev": "^7.1.5", + "@react-router/fs-routes": "^7.1.5", "@webstudio-is/image": "0.0.0-webstudio-version", "@webstudio-is/react-sdk": "0.0.0-webstudio-version", "@webstudio-is/sdk": "0.0.0-webstudio-version", @@ -22,7 +20,7 @@ "isbot": "^5.1.22", "react": "18.3.0-canary-14898b6a9-20240318", "react-dom": "18.3.0-canary-14898b6a9-20240318", - "react-router": "^7.1.4", + "react-router": "^7.1.5", "vite": "^5.4.11" }, "devDependencies": { diff --git a/packages/sdk-components-react-router/package.json b/packages/sdk-components-react-router/package.json index a6e5394f70d7..a913a1c5bf5f 100644 --- a/packages/sdk-components-react-router/package.json +++ b/packages/sdk-components-react-router/package.json @@ -42,7 +42,7 @@ "@webstudio-is/react-sdk": "workspace:*", "@webstudio-is/sdk": "workspace:*", "@webstudio-is/sdk-components-react": "workspace:*", - "react-router": "^7.1.4" + "react-router": "^7.1.5" }, "devDependencies": { "@types/react": "^18.2.70", diff --git a/packages/sdk/src/schema/deployment.ts b/packages/sdk/src/schema/deployment.ts index e577b53c1496..64413be5d499 100644 --- a/packages/sdk/src/schema/deployment.ts +++ b/packages/sdk/src/schema/deployment.ts @@ -4,6 +4,7 @@ export const Templates = z.enum([ "vanilla", "docker", "vercel", + "netlify", "netlify-functions", "netlify-edge-functions", "ssg", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19dc6862febf..2aea15b413cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -477,17 +477,17 @@ importers: fixtures/react-router-docker: dependencies: '@react-router/dev': - specifier: ^7.1.4 - version: 7.1.4(@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) + specifier: ^7.1.5 + version: 7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) '@react-router/fs-routes': - specifier: ^7.1.4 - version: 7.1.4(@react-router/dev@7.1.4(@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)))(typescript@5.7.3) + specifier: ^7.1.5 + version: 7.1.5(@react-router/dev@7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)))(typescript@5.7.3) '@react-router/node': - specifier: ^7.1.4 - version: 7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + specifier: ^7.1.5 + version: 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) '@react-router/serve': - specifier: ^7.1.4 - version: 7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + specifier: ^7.1.5 + version: 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) '@webstudio-is/image': specifier: workspace:* version: link:../../packages/image @@ -525,8 +525,72 @@ importers: specifier: 18.3.0-canary-14898b6a9-20240318 version: 18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318) react-router: - specifier: ^7.1.4 - version: 7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + specifier: ^7.1.5 + version: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + vite: + specifier: ^5.4.11 + version: 5.4.11(@types/node@22.10.7) + webstudio: + specifier: workspace:* + version: link:../../packages/cli + devDependencies: + '@types/react': + specifier: ^18.2.70 + version: 18.2.79 + '@types/react-dom': + specifier: ^18.2.25 + version: 18.2.25 + typescript: + specifier: 5.7.3 + version: 5.7.3 + + fixtures/react-router-netlify: + dependencies: + '@netlify/vite-plugin-react-router': + specifier: ^1.0.0 + version: 1.0.0(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318)(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7)) + '@react-router/dev': + specifier: ^7.1.5 + version: 7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) + '@react-router/fs-routes': + specifier: ^7.1.5 + version: 7.1.5(@react-router/dev@7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)))(typescript@5.7.3) + '@react-router/node': + specifier: ^7.1.5 + version: 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + '@webstudio-is/image': + specifier: workspace:* + version: link:../../packages/image + '@webstudio-is/react-sdk': + specifier: workspace:* + version: link:../../packages/react-sdk + '@webstudio-is/sdk': + specifier: workspace:* + version: link:../../packages/sdk + '@webstudio-is/sdk-components-animation': + specifier: workspace:* + version: link:../../packages/sdk-components-animation + '@webstudio-is/sdk-components-react': + specifier: workspace:* + version: link:../../packages/sdk-components-react + '@webstudio-is/sdk-components-react-radix': + specifier: workspace:* + version: link:../../packages/sdk-components-react-radix + '@webstudio-is/sdk-components-react-router': + specifier: workspace:* + version: link:../../packages/sdk-components-react-router + isbot: + specifier: ^5.1.22 + version: 5.1.22 + react: + specifier: 18.3.0-canary-14898b6a9-20240318 + version: 18.3.0-canary-14898b6a9-20240318 + react-dom: + specifier: 18.3.0-canary-14898b6a9-20240318 + version: 18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318) + react-router: + specifier: ^7.1.5 + version: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.10.7) @@ -1157,12 +1221,15 @@ importers: '@netlify/remix-edge-adapter': specifier: 3.4.2 version: 3.4.2(@remix-run/react@2.15.2(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318)(typescript@5.7.3))(@remix-run/serve@2.15.2(typescript@5.7.3))(@remix-run/server-runtime@2.15.2(typescript@5.7.3))(@types/node@22.10.7)(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) + '@netlify/vite-plugin-react-router': + specifier: ^1.0.0 + version: 1.0.0(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318)(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7)) '@react-router/dev': - specifier: ^7.1.4 - version: 7.1.4(@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) + specifier: ^7.1.5 + version: 7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) '@react-router/fs-routes': - specifier: ^7.1.4 - version: 7.1.4(@react-router/dev@7.1.4(@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)))(typescript@5.7.3) + specifier: ^7.1.5 + version: 7.1.5(@react-router/dev@7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)))(typescript@5.7.3) '@remix-run/cloudflare': specifier: ^2.15.2 version: 2.15.2(@cloudflare/workers-types@4.20240701.0)(typescript@5.7.3) @@ -1239,8 +1306,8 @@ importers: specifier: 18.3.0-canary-14898b6a9-20240318 version: 18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318) react-router: - specifier: ^7.1.4 - version: 7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + specifier: ^7.1.5 + version: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) ts-expect: specifier: ^1.3.0 version: 1.3.0 @@ -2165,8 +2232,8 @@ importers: specifier: workspace:* version: link:../sdk-components-react react-router: - specifier: ^7.1.4 - version: 7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + specifier: ^7.1.5 + version: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) devDependencies: '@types/react': specifier: ^18.2.70 @@ -3811,6 +3878,12 @@ packages: resolution: {integrity: sha512-q3L9i3HoNfz0SGpTIS4zTcKBbRkxzCRpd169eyiTuk3IwcPC3/85mzLHranlKo2b+HYT0gu37YxGB45aD8A3Tw==} engines: {node: '>=18.0.0'} + '@netlify/vite-plugin-react-router@1.0.0': + resolution: {integrity: sha512-J9xzn2Gdehg3LSEXFrpXLjQm2T2HQEnnlbTH7Ic9mjaz9rwkRmgwbYBn9h5SexQ0mV82jZjhvneblL8VPKiUtQ==} + engines: {node: '>=18'} + peerDependencies: + vite: '>=5.0.0' + '@nkzw/use-relative-time@1.1.0': resolution: {integrity: sha512-ogCL62FvScpRpsZUuaN6Jt0xPGRv62atQUNGyMcX+nZs4H5Fs5K1iA3MbSmkJ1y0n/N0RIRLc3VAp8o46lq2CA==} peerDependencies: @@ -4643,13 +4716,13 @@ packages: react: 18.3.0-canary-14898b6a9-20240318 react-dom: 18.3.0-canary-14898b6a9-20240318 - '@react-router/dev@7.1.4': - resolution: {integrity: sha512-im6/StFR5Dfe3Y4v+TTDguenjlb7GOcZGF0id/0BAeTrka3Q3wAJhkq84E+9G3jnu3JujoP7Cl1QOt/+Sc/9hA==} + '@react-router/dev@7.1.5': + resolution: {integrity: sha512-3YQAbaKQm4mxcd0jmbItr2Ik0GshEMmPpRAld7XhIymB50MklWSsgd+IJWcqSv8RVlNs1ZMxRC0maS3Hv/V19g==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@react-router/serve': ^7.1.4 - react-router: ^7.1.4 + '@react-router/serve': ^7.1.5 + react-router: ^7.1.5 typescript: ^5.1.0 vite: ^5.1.0 || ^6.0.0 wrangler: ^3.28.2 @@ -4661,43 +4734,43 @@ packages: wrangler: optional: true - '@react-router/express@7.1.4': - resolution: {integrity: sha512-mRQENitGitHohLLwu/vUahu463KO6jx8tPJIHhmbBblQk+Fyxs5ernJHxJe1ahm/BS1heGVGn1geWOLUuZ/+uw==} + '@react-router/express@7.1.5': + resolution: {integrity: sha512-k9aGrvPwCP+8CeHPxRaIqYKJi3xVzdN4QXFdZ++PPcPNy5/g8pM7GBAxWyUYH26+aDO8AqjzgbGgph2H0MN7kQ==} engines: {node: '>=20.0.0'} peerDependencies: express: ^4.17.1 - react-router: 7.1.4 + react-router: 7.1.5 typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true - '@react-router/fs-routes@7.1.4': - resolution: {integrity: sha512-EBf2hefhKbyt+5dkxRntqC5rFGLw1K91EkMGhX2dfvybWWQYGb98if6PDUMeU/h4F4h5VKV1VrHd6MMyGpuvgQ==} + '@react-router/fs-routes@7.1.5': + resolution: {integrity: sha512-Ncu8WHZNGKPXSKQZyoSRJsZaV9FnzbX3g/NRPzY88O7zwvURjebgMpq8K4rNdvbacTHg6/3umIFeCS0K9HdWPQ==} engines: {node: '>=20.0.0'} peerDependencies: - '@react-router/dev': ^7.1.4 + '@react-router/dev': ^7.1.5 typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true - '@react-router/node@7.1.4': - resolution: {integrity: sha512-TeCZMrFmMSjLI2HeUalL44P48+AEJyzO3lIUbR0ucFKq95tB0hu+X8daaLQlpbwGK4dL5i27kHiGiuMhVbynOQ==} + '@react-router/node@7.1.5': + resolution: {integrity: sha512-Ga8xFHxO2yt5TpGwV5xYx4LC3eUDmhT6jYfTbMFb6F7hBA9sLdHxNfYZCe2WEfVZ4/BM7I8989Qzq6BWilV2LA==} engines: {node: '>=20.0.0'} peerDependencies: - react-router: 7.1.4 + react-router: 7.1.5 typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true - '@react-router/serve@7.1.4': - resolution: {integrity: sha512-dU21Zz25hHxxdTsdOQqUy/gZLSYHb+TACrDVYPpIQxD1iByE9/mdNUeGUcb/zYnC0WgqYYbZp3SE9fDRvYscEA==} + '@react-router/serve@7.1.5': + resolution: {integrity: sha512-hx3oplonn3ByCeA8vDMm9Dohkbr63d8A0+mRRPBazWWoNd/kujUvodH6mzb8yDs74ppyAjw9iHAMVDdwLdrhCA==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - react-router: 7.1.4 + react-router: 7.1.5 '@react-stately/utils@3.10.5': resolution: {integrity: sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==} @@ -8164,8 +8237,8 @@ packages: peerDependencies: react: 18.3.0-canary-14898b6a9-20240318 - react-router@7.1.4: - resolution: {integrity: sha512-aJWVrKoLI0nIK1lfbTU3d5al1ZEUiwtSus/xjYL8K5sv2hyPesiOIojHM7QnaNLVtroOB1McZsWk37fMQVoc6A==} + react-router@7.1.5: + resolution: {integrity: sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==} engines: {node: '>=20.0.0'} peerDependencies: react: 18.3.0-canary-14898b6a9-20240318 @@ -10817,6 +10890,17 @@ snapshots: '@netlify/node-cookies': 0.1.0 urlpattern-polyfill: 8.0.2 + '@netlify/vite-plugin-react-router@1.0.0(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318)(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))': + dependencies: + '@react-router/node': 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + isbot: 5.1.22 + react-router: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + vite: 5.4.11(@types/node@22.10.7) + transitivePeerDependencies: + - react + - react-dom + - typescript + '@nkzw/use-relative-time@1.1.0(react@18.3.0-canary-14898b6a9-20240318)': dependencies: react: 18.3.0-canary-14898b6a9-20240318 @@ -11673,7 +11757,7 @@ snapshots: react: 18.3.0-canary-14898b6a9-20240318 react-dom: 18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318) - '@react-router/dev@7.1.4(@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0))': + '@react-router/dev@7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0))': dependencies: '@babel/core': 7.26.0 '@babel/generator': 7.26.2 @@ -11684,7 +11768,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 '@npmcli/package-json': 4.0.1 - '@react-router/node': 7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + '@react-router/node': 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) arg: 5.0.2 babel-dead-code-elimination: 1.0.8 chokidar: 4.0.3 @@ -11700,14 +11784,14 @@ snapshots: picomatch: 2.3.1 prettier: 2.8.7 react-refresh: 0.14.2 - react-router: 7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + react-router: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) semver: 7.6.3 set-cookie-parser: 2.6.0 valibot: 0.41.0(typescript@5.7.3) vite: 5.4.11(@types/node@22.10.7) vite-node: 3.0.0-beta.2(@types/node@22.10.7) optionalDependencies: - '@react-router/serve': 7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + '@react-router/serve': 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) typescript: 5.7.3 wrangler: 3.63.2(@cloudflare/workers-types@4.20240701.0) transitivePeerDependencies: @@ -11723,40 +11807,40 @@ snapshots: - supports-color - terser - '@react-router/express@7.1.4(express@4.21.1)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)': + '@react-router/express@7.1.5(express@4.21.1)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)': dependencies: - '@react-router/node': 7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + '@react-router/node': 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) express: 4.21.1 - react-router: 7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + react-router: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) optionalDependencies: typescript: 5.7.3 - '@react-router/fs-routes@7.1.4(@react-router/dev@7.1.4(@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)))(typescript@5.7.3)': + '@react-router/fs-routes@7.1.5(@react-router/dev@7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)))(typescript@5.7.3)': dependencies: - '@react-router/dev': 7.1.4(@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) + '@react-router/dev': 7.1.5(@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3))(@types/node@22.10.7)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)(vite@5.4.11(@types/node@22.10.7))(wrangler@3.63.2(@cloudflare/workers-types@4.20240701.0)) minimatch: 9.0.4 optionalDependencies: typescript: 5.7.3 - '@react-router/node@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)': + '@react-router/node@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + react-router: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) source-map-support: 0.5.21 stream-slice: 0.1.2 undici: 6.21.0 optionalDependencies: typescript: 5.7.3 - '@react-router/serve@7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)': + '@react-router/serve@7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3)': dependencies: - '@react-router/express': 7.1.4(express@4.21.1)(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) - '@react-router/node': 7.1.4(react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + '@react-router/express': 7.1.5(express@4.21.1)(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) + '@react-router/node': 7.1.5(react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318))(typescript@5.7.3) compression: 1.7.4 express: 4.21.1 get-port: 5.1.1 morgan: 1.10.0 - react-router: 7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) + react-router: 7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) source-map-support: 0.5.21 transitivePeerDependencies: - supports-color @@ -16062,7 +16146,7 @@ snapshots: '@remix-run/router': 1.21.0 react: 18.3.0-canary-14898b6a9-20240318 - react-router@7.1.4(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318): + react-router@7.1.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318): dependencies: '@types/cookie': 0.6.0 cookie: 1.0.1