mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-28 15:46:58 +09:00
experimental: SSG download support (#3783)
## Description See corresponding [SaaS PR](https://github.com/webstudio-is/webstudio-saas/pull/311) too ## Steps for reproduction Click Publish/Export <img width="363" alt="image" src="https://github.com/user-attachments/assets/fbc1f4e5-3a2c-4bb9-9242-025196f991bc"> Deploy zip to netlify <img width="1710" alt="image" src="https://github.com/user-attachments/assets/daca4ead-6272-4729-8e82-0986301e5c8b"> ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
This commit is contained in:
1 parent
1f435cc7f9
commit
8be665e68d
38 files changed
+701
-73
No files matched your search
@@ -0,0 +1,104 @@
|
||||
name: CLI R2 SSG
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*.staging"
|
||||
|
||||
# cancel in-progress runs on new commits to same PR (gitub.event.number)
|
||||
concurrency:
|
||||
group: vercel-cli-r2-static-${{ github.workflow }}-${{ github.event.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
statuses: write # This is required for the GitHub Script createCommitStatus to work
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
COMPATIBILITY_DATE: 2024-04-10
|
||||
|
||||
environment:
|
||||
name: "staging"
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.sha }} # HEAD commit instead of merge commit
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
- name: pnpm instal
|
||||
run: pnpm install --ignore-scripts
|
||||
|
||||
- name: pnpm build
|
||||
run: pnpm --filter 'ssg^...' run build
|
||||
|
||||
# Ideally, execute 'pnpm deploy --prod', but @remix-run/dev doesn't support this flag.
|
||||
# Despite being listed as a dependency, @remix-run/dev does not install the remix cli.
|
||||
# TODO: Minimize artefact size due to frequent downloads on each publish.
|
||||
- name: pnpm deploy
|
||||
run: pnpm --filter 'ssg' deploy "${{ github.workspace }}/../ssg-template"
|
||||
|
||||
- name: Make archive
|
||||
run: |
|
||||
tar --use-compress-program="zstd -19" -cf ssg-template.tar.zst ssg-template
|
||||
working-directory: ${{ github.workspace }}/..
|
||||
|
||||
- name: Copy artifact
|
||||
run: |
|
||||
# For staging
|
||||
aws s3 cp ssg-template.tar.zst "s3://${ARTEFACT_BUCKET_NAME}/public/ssg-template/${{ github.ref_name }}.tar.zst"
|
||||
|
||||
# For production can be cached forever
|
||||
aws s3 cp \
|
||||
--metadata-directive REPLACE --cache-control "public,max-age=31536102,immutable" \
|
||||
ssg-template.tar.zst "s3://${ARTEFACT_BUCKET_NAME}/public/ssg-template/${{ github.sha }}.tar.zst"
|
||||
|
||||
working-directory: ${{ github.workspace }}/..
|
||||
env:
|
||||
AWS_ENDPOINT_URL_S3: ${{ secrets.AWS_ENDPOINT_URL_S3 }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
ARTEFACT_BUCKET_NAME: ${{ secrets.ARTEFACT_BUCKET_NAME }}
|
||||
|
||||
checks:
|
||||
environment:
|
||||
name: "staging"
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: "9"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Copy atrifact via http
|
||||
run: curl -o ssg-template.tar.zst ${{ secrets.ARTEFACT_BUCKET_URL }}/public/ssg-template/${{ github.ref_name }}.tar.zst
|
||||
|
||||
- name: Extract archive
|
||||
run: tar --use-compress-program="zstd -d" -xf ssg-template.tar.zst -C .
|
||||
|
||||
- name: Webstudio Build
|
||||
run: pnpm webstudio build --template ssg --template internal --preview
|
||||
working-directory: ${{ github.workspace }}/ssg-template
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
working-directory: ${{ github.workspace }}/ssg-template
|
||||
@@ -83,7 +83,8 @@ export const getStatus = (projectDomain: Domain) =>
|
||||
? (`VERIFIED_${projectDomain.domain.status}` as const)
|
||||
: `UNVERIFIED`;
|
||||
|
||||
export const PENDING_TIMEOUT = 60 * 3 * 1000;
|
||||
export const PENDING_TIMEOUT =
|
||||
process.env.NODE_ENV === "production" ? 60 * 3 * 1000 : 35000;
|
||||
|
||||
export const getPublishStatusAndText = ({
|
||||
updatedAt,
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useOptimistic,
|
||||
useTransition,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { useStore } from "@nanostores/react";
|
||||
import {
|
||||
Button,
|
||||
@@ -25,6 +33,7 @@ import {
|
||||
Link,
|
||||
PanelBanner,
|
||||
buttonStyle,
|
||||
toast,
|
||||
} from "@webstudio-is/design-system";
|
||||
import stripIndent from "strip-indent";
|
||||
import {
|
||||
@@ -49,7 +58,8 @@ import {
|
||||
} from "@webstudio-is/icons";
|
||||
import { AddDomain } from "./add-domain";
|
||||
import { humanizeString } from "~/shared/string-utils";
|
||||
import { trpcClient } from "~/shared/trpc/trpc-client";
|
||||
import { trpcClient, nativeClient } from "~/shared/trpc/trpc-client";
|
||||
import { isFeatureEnabled } from "@webstudio-is/feature-flags";
|
||||
|
||||
type ProjectData =
|
||||
| {
|
||||
@@ -326,6 +336,7 @@ const Publish = ({
|
||||
domains: domainsToPublish.map(
|
||||
(projectDomain) => projectDomain.domain.domain
|
||||
),
|
||||
destination: "saas",
|
||||
},
|
||||
() => {
|
||||
refresh();
|
||||
@@ -340,6 +351,148 @@ const Publish = ({
|
||||
);
|
||||
};
|
||||
|
||||
const fetchProjectDataStatus = async (projectId: Project["id"]) => {
|
||||
const projectData = await nativeClient.domain.project.query({
|
||||
projectId,
|
||||
});
|
||||
|
||||
if (projectData.success === false) {
|
||||
throw new Error(projectData.error);
|
||||
}
|
||||
|
||||
if (projectData.project.latestStaticBuild == null) {
|
||||
return {
|
||||
status: "INITIAL" as const,
|
||||
statusText: "Not published",
|
||||
};
|
||||
}
|
||||
|
||||
const { status, statusText } = getPublishStatusAndText(
|
||||
projectData.project.latestStaticBuild
|
||||
);
|
||||
|
||||
return { status, statusText };
|
||||
};
|
||||
|
||||
type StaticProjectStatus = Awaited<ReturnType<typeof fetchProjectDataStatus>>;
|
||||
|
||||
const PublishStatic = ({ projectId }: { projectId: Project["id"] }) => {
|
||||
const [_, startTransition] = useTransition();
|
||||
|
||||
const isCanceledRef = useRef(false);
|
||||
useEffect(() => {
|
||||
isCanceledRef.current = false;
|
||||
return () => {
|
||||
isCanceledRef.current = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [projectDataStatus, setProjectDataStatus] =
|
||||
useState<StaticProjectStatus>({
|
||||
status: "INITIAL",
|
||||
statusText: "Not published",
|
||||
});
|
||||
|
||||
const [optimisticPendingStatus, setOptimisticPendingStatus] = useOptimistic(
|
||||
projectDataStatus,
|
||||
(_currentState, optimisticValue: StaticProjectStatus) => {
|
||||
return optimisticValue;
|
||||
}
|
||||
);
|
||||
|
||||
const isPublishInProgress = optimisticPendingStatus.status === "PENDING";
|
||||
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
paddingBottom: theme.spacing[5],
|
||||
paddingTop: theme.spacing[5],
|
||||
}}
|
||||
gap={2}
|
||||
shrink={false}
|
||||
direction={"column"}
|
||||
>
|
||||
{optimisticPendingStatus.status === "FAILED" && (
|
||||
<Text color="destructive">{optimisticPendingStatus.statusText}</Text>
|
||||
)}
|
||||
|
||||
<Tooltip
|
||||
content={isPublishInProgress ? "Preparing static site" : undefined}
|
||||
>
|
||||
<Button
|
||||
color="positive"
|
||||
state={isPublishInProgress ? "pending" : undefined}
|
||||
onClick={() => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
setOptimisticPendingStatus({
|
||||
status: "PENDING",
|
||||
statusText: "Publishing",
|
||||
});
|
||||
|
||||
const result = await nativeClient.domain.publish.mutate({
|
||||
projectId,
|
||||
destination: "static",
|
||||
});
|
||||
|
||||
if (result.success === false) {
|
||||
toast.error(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const name = "name" in result ? result.name : undefined;
|
||||
|
||||
if (name == null) {
|
||||
toast.error('File name must be defined in "result"');
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = 10000;
|
||||
|
||||
// Repeat few more times than timeout
|
||||
const repeat = PENDING_TIMEOUT / timeout + 5;
|
||||
|
||||
let projectDataStatus: StaticProjectStatus | undefined;
|
||||
|
||||
for (let i = 0; i !== repeat; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, timeout));
|
||||
|
||||
if (isCanceledRef.current) {
|
||||
break;
|
||||
}
|
||||
|
||||
projectDataStatus = await fetchProjectDataStatus(projectId);
|
||||
|
||||
if (isCanceledRef.current) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (projectDataStatus.status !== "PENDING") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (projectDataStatus == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setProjectDataStatus(projectDataStatus);
|
||||
if (projectDataStatus.status === "PUBLISHED") {
|
||||
window.location.href = `/cgi/static/ssg/${name}`;
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "Unknown error");
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Build and download static site
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorText = ({ children }: { children: string }) => (
|
||||
<Flex
|
||||
css={{
|
||||
@@ -372,6 +525,7 @@ const Content = (props: {
|
||||
onExportClick: () => void;
|
||||
}) => {
|
||||
const [newDomains, setNewDomains] = useState(new Set<string>());
|
||||
|
||||
const {
|
||||
data: domainsResult,
|
||||
load: domainRefresh,
|
||||
@@ -487,11 +641,9 @@ const Content = (props: {
|
||||
<ErrorText>{domainsResult.error}</ErrorText>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
<Flex direction="column" justify="end" css={{ height: 0 }}>
|
||||
<Separator />
|
||||
</Flex>
|
||||
|
||||
<AddDomain
|
||||
projectId={props.projectId}
|
||||
refreshDomainResult={domainRefresh}
|
||||
@@ -504,8 +656,7 @@ const Content = (props: {
|
||||
isPublishing={isPublishing}
|
||||
onExportClick={props.onExportClick}
|
||||
/>
|
||||
|
||||
{projectData?.success === true ? (
|
||||
{projectData?.success === true && (
|
||||
<Publish
|
||||
project={projectData.project}
|
||||
domainsToPublish={domainsToPublish}
|
||||
@@ -516,7 +667,8 @@ const Content = (props: {
|
||||
isPublishing={isPublishing}
|
||||
setIsPublishing={setIsPublishing}
|
||||
/>
|
||||
) : (
|
||||
)}
|
||||
{projectData?.success !== true && (
|
||||
<Box css={{ height: theme.spacing[8] }} />
|
||||
)}
|
||||
</>
|
||||
@@ -543,7 +695,7 @@ type DeployTargets = keyof typeof deployTargets;
|
||||
const isDeployTargets = (value: string): value is DeployTargets =>
|
||||
Object.keys(deployTargets).includes(value);
|
||||
|
||||
const ExportContent = () => {
|
||||
const ExportContent = (props: { projectId: Project["id"] }) => {
|
||||
const npxCommand = "npx webstudio@latest";
|
||||
const [deployTarget, setDeployTarget] = useState<DeployTargets>("vercel");
|
||||
|
||||
@@ -557,6 +709,23 @@ const ExportContent = () => {
|
||||
}}
|
||||
>
|
||||
<Grid columns={1} gap={1}>
|
||||
{isFeatureEnabled("staticExport") && (
|
||||
<>
|
||||
<PublishStatic projectId={props.projectId} />
|
||||
<Grid
|
||||
gap={2}
|
||||
align={"center"}
|
||||
css={{
|
||||
gridTemplateColumns: `1fr auto 1fr`,
|
||||
}}
|
||||
>
|
||||
<Separator css={{ alignSelf: "unset" }} />
|
||||
<Text color="main">OR</Text>
|
||||
<Separator css={{ alignSelf: "unset" }} />
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text color="main" variant="labelsTitleCase">
|
||||
Step 1
|
||||
</Text>
|
||||
@@ -750,7 +919,7 @@ export const PublishButton = ({ projectId }: PublishProps) => {
|
||||
{dialogContentType === "export" && (
|
||||
<>
|
||||
<FloatingPanelPopoverTitle>Export</FloatingPanelPopoverTitle>
|
||||
<ExportContent />
|
||||
<ExportContent projectId={projectId} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -35,14 +35,17 @@ export const loadProductionCanvasData = async (
|
||||
|
||||
// Check that build deployment domains are still active and verified
|
||||
// for examle: redeploy created few days later
|
||||
const domains = deployment.domains.filter((domain) =>
|
||||
currentProjectDomains.some(
|
||||
(projectDomain) =>
|
||||
projectDomain.domain.domain === domain &&
|
||||
projectDomain.domain.status === "ACTIVE" &&
|
||||
projectDomain.verified
|
||||
)
|
||||
);
|
||||
const domains =
|
||||
deployment.destination === "static"
|
||||
? []
|
||||
: deployment.domains.filter((domain) =>
|
||||
currentProjectDomains.some(
|
||||
(projectDomain) =>
|
||||
projectDomain.domain.domain === domain &&
|
||||
projectDomain.domain.status === "ACTIVE" &&
|
||||
projectDomain.verified
|
||||
)
|
||||
);
|
||||
|
||||
const page = findPageByIdOrPath("/", build.pages);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { createRecursiveProxy } from "@trpc/server/shared";
|
||||
import { useMemo, useState } from "react";
|
||||
import { $authToken } from "../nano-states";
|
||||
|
||||
const client = createTRPCProxyClient<AppRouter>({
|
||||
export const nativeClient = createTRPCProxyClient<AppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: "/trpc",
|
||||
@@ -99,9 +99,9 @@ export const trpcClient: {
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// }
|
||||
|
||||
const result = await (client as unknown as Client)[namespace][method][
|
||||
hook === "useMutation" ? "mutate" : "query"
|
||||
](input);
|
||||
const result = await (nativeClient as unknown as Client)[namespace][
|
||||
method
|
||||
][hook === "useMutation" ? "mutate" : "query"](input);
|
||||
|
||||
// Newer request has been made, ignore this one
|
||||
// In the future to support optimistic updates we can call onComplete with the requestId order
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"include": ["app", "**/*.ts", "**/*.tsx", "../../@types/**/*.d.ts"],
|
||||
|
||||
"compilerOptions": {
|
||||
"types": ["@remix-run/node", "vite/client"],
|
||||
"types": ["@remix-run/node", "vite/client", "react/canary"],
|
||||
"paths": {
|
||||
"~/*": ["app/*"]
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"build": {
|
||||
"id": "2b8a9d28-3a0e-48c9-9aa2-b26db7926a22",
|
||||
"id": "a2e8de30-03d5-4514-a3a6-406b3266a3af",
|
||||
"projectId": "d845c167-ea07-4875-b08d-83e97c09dcce",
|
||||
"version": 33,
|
||||
"createdAt": "2024-07-26T13:42:42.816",
|
||||
"updatedAt": "2024-07-26T13:42:42.816",
|
||||
"version": 38,
|
||||
"createdAt": "2024-07-29T12:50:07.515",
|
||||
"updatedAt": "2024-07-29T12:50:07.515",
|
||||
"pages": {
|
||||
"meta": {
|
||||
"siteName": "",
|
||||
@@ -25,6 +25,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
@@ -252,6 +253,16 @@
|
||||
"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": [
|
||||
@@ -298,6 +309,10 @@
|
||||
{
|
||||
"type": "id",
|
||||
"value": "uHB3Fjb7-NELG-bnH7bXB"
|
||||
},
|
||||
{
|
||||
"type": "id",
|
||||
"value": "2sIE8GxbKRBaav_zdhaZ1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -308,6 +323,7 @@
|
||||
"type": "instance",
|
||||
"id": "MYDt0guk1-vzc7yzqyN6A",
|
||||
"component": "Heading",
|
||||
"label": "xD",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
@@ -380,6 +396,15 @@
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
[
|
||||
"2sIE8GxbKRBaav_zdhaZ1",
|
||||
{
|
||||
"type": "instance",
|
||||
"id": "2sIE8GxbKRBaav_zdhaZ1",
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"deployment": {
|
||||
@@ -411,6 +436,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
export const sitemap = [
|
||||
{
|
||||
path: "/",
|
||||
lastModified: "2024-07-26",
|
||||
lastModified: "2024-07-29",
|
||||
},
|
||||
];
|
||||
+4
@@ -41,6 +41,10 @@ const Page = ({}: { system: any }) => {
|
||||
height={16}
|
||||
className="w-image c161qeci"
|
||||
/>
|
||||
<Image
|
||||
src={"https://picsum.photos/id/237/100/100.jpg?blur=4&grayscale"}
|
||||
className="w-image"
|
||||
/>
|
||||
</Body>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,5 +9,9 @@ export const imageBaseUrl = "/assets/";
|
||||
* @type {import("@webstudio-is/image").ImageLoader}
|
||||
*/
|
||||
export const imageLoader = ({ src }) => {
|
||||
if (URL.canParse(src)) {
|
||||
return src;
|
||||
}
|
||||
|
||||
return imageBaseUrl + src;
|
||||
};
|
||||
@@ -26,6 +26,7 @@
|
||||
"@types/react": "^18.2.70",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"prettier": "3.3.2",
|
||||
"typescript": "5.5.2",
|
||||
"vite": "^5.3.4"
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"build": {
|
||||
"id": "2b8a9d28-3a0e-48c9-9aa2-b26db7926a22",
|
||||
"id": "a2e8de30-03d5-4514-a3a6-406b3266a3af",
|
||||
"projectId": "d845c167-ea07-4875-b08d-83e97c09dcce",
|
||||
"version": 33,
|
||||
"createdAt": "2024-07-26T13:42:42.816",
|
||||
"updatedAt": "2024-07-26T13:42:42.816",
|
||||
"version": 38,
|
||||
"createdAt": "2024-07-29T12:50:07.515",
|
||||
"updatedAt": "2024-07-29T12:50:07.515",
|
||||
"pages": {
|
||||
"meta": {
|
||||
"siteName": "",
|
||||
@@ -25,6 +25,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
@@ -252,6 +253,16 @@
|
||||
"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": [
|
||||
@@ -298,6 +309,10 @@
|
||||
{
|
||||
"type": "id",
|
||||
"value": "uHB3Fjb7-NELG-bnH7bXB"
|
||||
},
|
||||
{
|
||||
"type": "id",
|
||||
"value": "2sIE8GxbKRBaav_zdhaZ1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -308,6 +323,7 @@
|
||||
"type": "instance",
|
||||
"id": "MYDt0guk1-vzc7yzqyN6A",
|
||||
"component": "Heading",
|
||||
"label": "xD",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
@@ -380,6 +396,15 @@
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
[
|
||||
"2sIE8GxbKRBaav_zdhaZ1",
|
||||
{
|
||||
"type": "instance",
|
||||
"id": "2sIE8GxbKRBaav_zdhaZ1",
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"deployment": {
|
||||
@@ -411,6 +436,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
export const sitemap = [
|
||||
{
|
||||
path: "/",
|
||||
lastModified: "2024-07-26",
|
||||
lastModified: "2024-07-29",
|
||||
},
|
||||
];
|
||||
@@ -43,6 +43,10 @@ const Page = ({}: { system: any }) => {
|
||||
height={16}
|
||||
className="w-image c161qeci"
|
||||
/>
|
||||
<Image
|
||||
src={"https://picsum.photos/id/237/100/100.jpg?blur=4&grayscale"}
|
||||
className="w-image"
|
||||
/>
|
||||
</Body>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"build": {
|
||||
"id": "2b8a9d28-3a0e-48c9-9aa2-b26db7926a22",
|
||||
"id": "a2e8de30-03d5-4514-a3a6-406b3266a3af",
|
||||
"projectId": "d845c167-ea07-4875-b08d-83e97c09dcce",
|
||||
"version": 33,
|
||||
"createdAt": "2024-07-26T13:42:42.816",
|
||||
"updatedAt": "2024-07-26T13:42:42.816",
|
||||
"version": 38,
|
||||
"createdAt": "2024-07-29T12:50:07.515",
|
||||
"updatedAt": "2024-07-29T12:50:07.515",
|
||||
"pages": {
|
||||
"meta": {
|
||||
"siteName": "",
|
||||
@@ -25,6 +25,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
@@ -252,6 +253,16 @@
|
||||
"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": [
|
||||
@@ -298,6 +309,10 @@
|
||||
{
|
||||
"type": "id",
|
||||
"value": "uHB3Fjb7-NELG-bnH7bXB"
|
||||
},
|
||||
{
|
||||
"type": "id",
|
||||
"value": "2sIE8GxbKRBaav_zdhaZ1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -308,6 +323,7 @@
|
||||
"type": "instance",
|
||||
"id": "MYDt0guk1-vzc7yzqyN6A",
|
||||
"component": "Heading",
|
||||
"label": "xD",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
@@ -380,6 +396,15 @@
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
[
|
||||
"2sIE8GxbKRBaav_zdhaZ1",
|
||||
{
|
||||
"type": "instance",
|
||||
"id": "2sIE8GxbKRBaav_zdhaZ1",
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"deployment": {
|
||||
@@ -411,6 +436,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
export const sitemap = [
|
||||
{
|
||||
path: "/",
|
||||
lastModified: "2024-07-26",
|
||||
lastModified: "2024-07-29",
|
||||
},
|
||||
];
|
||||
@@ -43,6 +43,10 @@ const Page = ({}: { system: any }) => {
|
||||
height={16}
|
||||
className="w-image c161qeci"
|
||||
/>
|
||||
<Image
|
||||
src={"https://picsum.photos/id/237/100/100.jpg?blur=4&grayscale"}
|
||||
className="w-image"
|
||||
/>
|
||||
</Body>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"build": {
|
||||
"id": "2b8a9d28-3a0e-48c9-9aa2-b26db7926a22",
|
||||
"id": "a2e8de30-03d5-4514-a3a6-406b3266a3af",
|
||||
"projectId": "d845c167-ea07-4875-b08d-83e97c09dcce",
|
||||
"version": 33,
|
||||
"createdAt": "2024-07-26T13:42:42.816",
|
||||
"updatedAt": "2024-07-26T13:42:42.816",
|
||||
"version": 38,
|
||||
"createdAt": "2024-07-29T12:50:07.515",
|
||||
"updatedAt": "2024-07-29T12:50:07.515",
|
||||
"pages": {
|
||||
"meta": {
|
||||
"siteName": "",
|
||||
@@ -25,6 +25,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
@@ -252,6 +253,16 @@
|
||||
"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": [
|
||||
@@ -298,6 +309,10 @@
|
||||
{
|
||||
"type": "id",
|
||||
"value": "uHB3Fjb7-NELG-bnH7bXB"
|
||||
},
|
||||
{
|
||||
"type": "id",
|
||||
"value": "2sIE8GxbKRBaav_zdhaZ1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -308,6 +323,7 @@
|
||||
"type": "instance",
|
||||
"id": "MYDt0guk1-vzc7yzqyN6A",
|
||||
"component": "Heading",
|
||||
"label": "xD",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
@@ -380,6 +396,15 @@
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
[
|
||||
"2sIE8GxbKRBaav_zdhaZ1",
|
||||
{
|
||||
"type": "instance",
|
||||
"id": "2sIE8GxbKRBaav_zdhaZ1",
|
||||
"component": "Image",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"deployment": {
|
||||
@@ -411,6 +436,7 @@
|
||||
"id": "WPPAbLFyJD_02vhjRd8P4",
|
||||
"name": "Another page",
|
||||
"title": "\"Another page\"",
|
||||
"history": ["/another-page"],
|
||||
"rootInstanceId": "n_VBMr7klpx25buS0NV7R",
|
||||
"systemDataSourceId": "tdXe9gFf83hSo9BLWU6xl",
|
||||
"meta": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
export const sitemap = [
|
||||
{
|
||||
path: "/",
|
||||
lastModified: "2024-07-26",
|
||||
lastModified: "2024-07-29",
|
||||
},
|
||||
];
|
||||
@@ -43,6 +43,10 @@ const Page = ({}: { system: any }) => {
|
||||
height={16}
|
||||
className="w-image c161qeci"
|
||||
/>
|
||||
<Image
|
||||
src={"https://picsum.photos/id/237/100/100.jpg?blur=4&grayscale"}
|
||||
className="w-image"
|
||||
/>
|
||||
</Body>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -296,11 +296,6 @@ export const prebuild = async (options: {
|
||||
);
|
||||
}
|
||||
|
||||
const domain = siteData.build.deployment?.projectDomain;
|
||||
if (domain === undefined) {
|
||||
throw new Error(`Project domain is missing from the project data`);
|
||||
}
|
||||
|
||||
// collect all possible component metas
|
||||
const metas = new Map<string, WsComponentMeta>();
|
||||
const componentSources = new Map<string, string>();
|
||||
@@ -448,6 +443,15 @@ export const prebuild = async (options: {
|
||||
const assetsToDownload: Promise<void>[] = [];
|
||||
|
||||
const appDomain = options.preview ? "wstd.work" : "wstd.io";
|
||||
|
||||
const domain =
|
||||
siteData.build.deployment?.destination === "static"
|
||||
? siteData.build.deployment?.assetsDomain
|
||||
: siteData.build.deployment?.projectDomain;
|
||||
if (domain === undefined) {
|
||||
throw new Error(`Project domain is missing from the project data`);
|
||||
}
|
||||
|
||||
const assetBuildUrl = `https://${domain}.${appDomain}/cgi/asset/`;
|
||||
|
||||
const imageLoader = createImageLoader({
|
||||
|
||||
@@ -9,5 +9,9 @@ export const imageBaseUrl = "/assets/";
|
||||
* @type {import("@webstudio-is/image").ImageLoader}
|
||||
*/
|
||||
export const imageLoader = ({ src }) => {
|
||||
if (URL.canParse(src)) {
|
||||
return src;
|
||||
}
|
||||
|
||||
return imageBaseUrl + src;
|
||||
};
|
||||
@@ -13,6 +13,7 @@
|
||||
"@webstudio-is/project": "workspace:*",
|
||||
"@webstudio-is/project-build": "workspace:*",
|
||||
"@webstudio-is/trpc-interface": "workspace:*",
|
||||
"nanoid": "^5.0.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { db as projectDb } from "@webstudio-is/project/index.server";
|
||||
import { db } from "../db";
|
||||
import { createProductionBuild } from "@webstudio-is/project-build/index.server";
|
||||
import { router, procedure } from "@webstudio-is/trpc-interface/index.server";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const domainRouter = router({
|
||||
getEntriToken: procedure.query(async ({ ctx }) => {
|
||||
@@ -41,35 +42,65 @@ export const domainRouter = router({
|
||||
}
|
||||
}),
|
||||
publish: procedure
|
||||
.input(z.object({ projectId: z.string(), domains: z.array(z.string()) }))
|
||||
.input(
|
||||
z.discriminatedUnion("destination", [
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
domains: z.array(z.string()),
|
||||
destination: z.literal("saas"),
|
||||
}),
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
destination: z.literal("static"),
|
||||
}),
|
||||
])
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const project = await projectDb.project.loadById(input.projectId, ctx);
|
||||
|
||||
const name = `${project.id}-${nanoid()}.zip`;
|
||||
|
||||
const build = await createProductionBuild(
|
||||
{
|
||||
projectId: input.projectId,
|
||||
deployment: {
|
||||
domains: input.domains,
|
||||
projectDomain: project.domain,
|
||||
},
|
||||
deployment:
|
||||
input.destination === "saas"
|
||||
? {
|
||||
destination: input.destination,
|
||||
domains: input.domains,
|
||||
projectDomain: project.domain,
|
||||
}
|
||||
: {
|
||||
destination: input.destination,
|
||||
name,
|
||||
assetsDomain: project.domain,
|
||||
templates: ["ssg"],
|
||||
},
|
||||
},
|
||||
ctx
|
||||
);
|
||||
|
||||
const { deploymentTrpc, env } = ctx.deployment;
|
||||
|
||||
const result = deploymentTrpc.publish.mutate({
|
||||
console.info("input.destination", input.destination);
|
||||
|
||||
const result = await deploymentTrpc.publish.mutate({
|
||||
// used to load build data from the builder see routes/rest.build.$buildId.ts
|
||||
builderOrigin: env.BUILDER_ORIGIN,
|
||||
githubSha: env.GITHUB_SHA,
|
||||
buildId: build.id,
|
||||
// preview support
|
||||
branchName: env.GITHUB_REF_NAME,
|
||||
destination: input.destination,
|
||||
// action log helper (not used for deployment, but for action logs readablity)
|
||||
projectDomainName: project.domain,
|
||||
});
|
||||
|
||||
if (input.destination === "static" && result.success) {
|
||||
return { success: true as const, name };
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return {
|
||||
|
||||
@@ -8,3 +8,4 @@ export const cssVars = false;
|
||||
export const filters = false;
|
||||
export const xmlElement = false;
|
||||
export const transforms = false;
|
||||
export const staticExport = false;
|
||||
+24
@@ -679,6 +679,30 @@ export type Database = {
|
||||
},
|
||||
];
|
||||
};
|
||||
LatestStaticBuildPerProject: {
|
||||
Row: {
|
||||
buildId: string | null;
|
||||
projectId: string | null;
|
||||
publishStatus: Database["public"]["Enums"]["PublishStatus"] | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "Build_projectId_fkey";
|
||||
columns: ["projectId"];
|
||||
isOneToOne: false;
|
||||
referencedRelation: "Project";
|
||||
referencedColumns: ["id"];
|
||||
},
|
||||
{
|
||||
foreignKeyName: "Build_projectId_fkey";
|
||||
columns: ["projectId"];
|
||||
isOneToOne: false;
|
||||
referencedRelation: "DashboardProject";
|
||||
referencedColumns: ["id"];
|
||||
},
|
||||
];
|
||||
};
|
||||
ProjectWithDomain: {
|
||||
Row: {
|
||||
cname: string | null;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
CREATE OR REPLACE VIEW "LatestBuildPerProject" AS
|
||||
WITH lb AS (
|
||||
SELECT DISTINCT ON ("projectId")
|
||||
bld.id AS "buildId",
|
||||
bld."projectId",
|
||||
bld."updatedAt"
|
||||
FROM
|
||||
"Build" bld
|
||||
WHERE
|
||||
bld.deployment IS NOT NULL
|
||||
AND bld.deployment::jsonb ->> 'projectDomain'::text IS NOT NULL
|
||||
ORDER BY
|
||||
bld."projectId",
|
||||
bld."createdAt" DESC,
|
||||
"buildId"
|
||||
)
|
||||
SELECT DISTINCT ON ("projectId", "domain")
|
||||
bld.id AS "buildId",
|
||||
bld."projectId",
|
||||
deployment::jsonb ->> 'projectDomain' AS domain,
|
||||
coalesce(bld."updatedAt" = lb."updatedAt", FALSE) AS "isLatestBuild",
|
||||
bld."updatedAt",
|
||||
bld."publishStatus"
|
||||
FROM
|
||||
"Build" bld,
|
||||
lb
|
||||
WHERE
|
||||
bld.deployment IS NOT NULL
|
||||
AND lb."projectId" = bld."projectId"
|
||||
AND bld.deployment::jsonb ->> 'projectDomain'::text IS NOT NULL
|
||||
ORDER BY
|
||||
bld."projectId",
|
||||
"domain",
|
||||
bld."createdAt" DESC,
|
||||
"buildId";
|
||||
|
||||
|
||||
CREATE OR REPLACE VIEW "LatestBuildPerProjectDomain" AS
|
||||
WITH lbd AS (
|
||||
SELECT DISTINCT ON ("projectId",
|
||||
"domain")
|
||||
jsonb_array_elements_text(deployment::jsonb -> 'domains') AS "domain",
|
||||
bld.id AS "buildId",
|
||||
bld."projectId",
|
||||
bld."updatedAt",
|
||||
bld."publishStatus"
|
||||
FROM
|
||||
"Build" bld
|
||||
WHERE
|
||||
bld.deployment IS NOT NULL
|
||||
ORDER BY
|
||||
bld."projectId",
|
||||
"domain",
|
||||
bld."createdAt" DESC,
|
||||
"buildId"
|
||||
),
|
||||
lb AS (
|
||||
SELECT DISTINCT ON ("projectId")
|
||||
bld.id AS "buildId",
|
||||
bld."projectId",
|
||||
bld."updatedAt",
|
||||
bld."publishStatus"
|
||||
FROM
|
||||
"Build" bld
|
||||
WHERE
|
||||
bld.deployment IS NOT NULL
|
||||
AND bld.deployment::jsonb ->> 'projectDomain'::text IS NOT NULL
|
||||
ORDER BY
|
||||
bld."projectId",
|
||||
bld."createdAt" DESC,
|
||||
"buildId"
|
||||
)
|
||||
SELECT
|
||||
d.id AS "domainId",
|
||||
lbd."projectId",
|
||||
lbd."buildId",
|
||||
coalesce(lbd."updatedAt" = lb."updatedAt", FALSE) AS "isLatestBuild",
|
||||
lbd."publishStatus",
|
||||
lbd."updatedAt"
|
||||
FROM
|
||||
lbd,
|
||||
lb,
|
||||
"Domain" d
|
||||
WHERE
|
||||
lbd.domain = d.domain
|
||||
AND lb."projectId" = lbd."projectId";
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
CREATE OR REPLACE VIEW "LatestStaticBuildPerProject" AS
|
||||
SELECT DISTINCT ON ("projectId")
|
||||
bld.id AS "buildId",
|
||||
bld."projectId",
|
||||
bld."updatedAt",
|
||||
bld."publishStatus"
|
||||
FROM
|
||||
"Build" bld
|
||||
WHERE
|
||||
bld.deployment IS NOT NULL
|
||||
AND bld.deployment::jsonb ->> 'destination'::text = 'static'
|
||||
ORDER BY
|
||||
bld."projectId",
|
||||
bld."createdAt" DESC,
|
||||
"buildId";
|
||||
|
||||
@@ -139,21 +139,22 @@ view UserProduct {
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
title String
|
||||
domain String @unique
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
domain String @unique
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId String?
|
||||
build Build[]
|
||||
isDeleted Boolean @default(false)
|
||||
isDeleted Boolean @default(false)
|
||||
files File[]
|
||||
projectDomain ProjectDomain[]
|
||||
latestBuild LatestBuildPerProject?
|
||||
authorizationToken AuthorizationToken[]
|
||||
previewImageAsset Asset? @relation(fields: [previewImageAssetId, id], references: [id, projectId])
|
||||
previewImageAsset Asset? @relation(fields: [previewImageAssetId, id], references: [id, projectId])
|
||||
previewImageAssetId String?
|
||||
marketplaceApprovalStatus MarketplaceApprovalStatus @default(UNLISTED)
|
||||
marketplaceApprovalStatus MarketplaceApprovalStatus @default(UNLISTED)
|
||||
latestStaticBuild LatestStaticBuildPerProject?
|
||||
|
||||
@@unique([id, isDeleted])
|
||||
@@unique([domain, isDeleted])
|
||||
@@ -199,7 +200,8 @@ model Build {
|
||||
deployment String?
|
||||
publishStatus PublishStatus @default(PENDING)
|
||||
|
||||
latestBuildPerProject LatestBuildPerProject?
|
||||
latestBuildPerProject LatestBuildPerProject?
|
||||
latestStaticBuildPerProject LatestStaticBuildPerProject?
|
||||
|
||||
@@id([id, projectId])
|
||||
// ApprovedMarketplaceProduct view performance index
|
||||
@@ -322,6 +324,20 @@ view LatestBuildPerProject {
|
||||
@@unique([buildId, projectId])
|
||||
}
|
||||
|
||||
view LatestStaticBuildPerProject {
|
||||
buildId String
|
||||
build Build @relation(fields: [buildId, projectId], references: [id, projectId])
|
||||
|
||||
projectId String
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
|
||||
publishStatus PublishStatus
|
||||
updatedAt DateTime
|
||||
|
||||
@@id([projectId])
|
||||
@@unique([buildId, projectId])
|
||||
}
|
||||
|
||||
// Dashboard
|
||||
view DashboardProject {
|
||||
id String @id @default(uuid())
|
||||
|
||||
@@ -25,6 +25,7 @@ export const loadById = async (
|
||||
where: { id_isDeleted: { id: projectId, isDeleted: false } },
|
||||
include: {
|
||||
latestBuild: true,
|
||||
latestStaticBuild: true,
|
||||
previewImageAsset: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -43,6 +43,15 @@ export const Project = z.object({
|
||||
})
|
||||
.nullable()
|
||||
),
|
||||
latestStaticBuild: z.optional(
|
||||
z
|
||||
.object({
|
||||
buildId: z.string(),
|
||||
publishStatus: z.enum(["PENDING", "PUBLISHED", "FAILED"]),
|
||||
updatedAt: z.date().transform((date) => date.toISOString()),
|
||||
})
|
||||
.nullable()
|
||||
),
|
||||
marketplaceApprovalStatus: MarketplaceApprovalStatus,
|
||||
});
|
||||
|
||||
|
||||
@@ -17,19 +17,19 @@ export const RemixForm = forwardRef<
|
||||
// resulting in our UI displaying a list that encompasses both variants.
|
||||
method?: Lowercase<NonNullable<FormProps["method"]>>;
|
||||
}
|
||||
>((props, ref) => {
|
||||
>(({ action, ...props }, ref) => {
|
||||
const { renderer } = useContext(ReactSdkContext);
|
||||
// remix casts action to relative url
|
||||
if (
|
||||
props.action === undefined ||
|
||||
props.action === "" ||
|
||||
props.action?.startsWith("/")
|
||||
action === undefined ||
|
||||
action === "" ||
|
||||
(typeof action === "string" && action?.startsWith("/"))
|
||||
) {
|
||||
// remix forms specifies own action instead of provided one
|
||||
// which makes it hard to handle intercepted submit events
|
||||
// render remix form only in published sites
|
||||
if (renderer !== "canvas" && renderer !== "preview") {
|
||||
return <Form {...props} ref={ref} />;
|
||||
return <Form action={action} {...props} ref={ref} />;
|
||||
}
|
||||
}
|
||||
return <form {...props} ref={ref} />;
|
||||
|
||||
@@ -120,7 +120,11 @@ export const WebhookForm = forwardRef<
|
||||
ref={ref}
|
||||
onSubmit={handleSubmitAndAddHiddenJsField}
|
||||
>
|
||||
<input type="hidden" name={formIdFieldName} value={action} />
|
||||
<input
|
||||
type="hidden"
|
||||
name={formIdFieldName}
|
||||
value={action?.toString()}
|
||||
/>
|
||||
{children}
|
||||
</fetcher.Form>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
{
|
||||
"extends": "@webstudio-is/tsconfig/base.json"
|
||||
"extends": "@webstudio-is/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["react/canary", "@types/node"]
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,15 @@ export const Image = forwardRef<
|
||||
assetName = src.slice(assetBaseUrl.length);
|
||||
}
|
||||
|
||||
console.info(
|
||||
"assetName",
|
||||
assetName,
|
||||
"src",
|
||||
src,
|
||||
"assetBaseUrl",
|
||||
assetBaseUrl
|
||||
);
|
||||
|
||||
return (
|
||||
<WebstudioImage
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const Deployment = z.object({
|
||||
domains: z.array(z.string()),
|
||||
projectDomain: z.string(),
|
||||
});
|
||||
export const Deployment = z.union([
|
||||
z.object({
|
||||
destination: z.literal("static"),
|
||||
name: z.string(),
|
||||
assetsDomain: z.string(),
|
||||
templates: z.array(z.string()),
|
||||
}),
|
||||
z.object({
|
||||
destination: z.literal("saas").optional(),
|
||||
domains: z.array(z.string()),
|
||||
projectDomain: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type Deployment = z.infer<typeof Deployment>;
|
||||
@@ -7,6 +7,7 @@ const PublishInput = z.object({
|
||||
builderOrigin: z.string(),
|
||||
githubSha: z.string().optional(),
|
||||
|
||||
destination: z.enum(["saas", "static"]),
|
||||
// preview support
|
||||
branchName: z.string(),
|
||||
// action log helper (not used for deployment, but for action logs readablity)
|
||||
|
||||
Generated
+6
@@ -492,6 +492,9 @@ importers:
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1(vite@5.3.4(@types/node@20.12.7))
|
||||
prettier:
|
||||
specifier: 3.3.2
|
||||
version: 3.3.2
|
||||
typescript:
|
||||
specifier: 5.5.2
|
||||
version: 5.5.2
|
||||
@@ -1399,6 +1402,9 @@ importers:
|
||||
'@webstudio-is/trpc-interface':
|
||||
specifier: workspace:*
|
||||
version: link:../trpc-interface
|
||||
nanoid:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
|
||||
Reference in new issue
Block a user