Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions .github/actions/vercel/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ inputs:
description: "Sha"
required: true
environment:
description: "Sha"
description: "Alias environment"
required: true
production-staged:
description: "Create a production deployment without assigning production domains"
required: false
default: "false"
PUBLIC_COLLAB_RELAY_URL:
description: "Public collaboration relay URL injected into the builder bundle"
required: false
Expand Down Expand Up @@ -79,9 +83,14 @@ runs:
run: |
export GITHUB_SHA=${{ inputs.sha }}
export GITHUB_REF_NAME=${{ inputs.ref-name }}
export PUBLIC_COLLAB_RELAY_URL=${{ inputs.PUBLIC_COLLAB_RELAY_URL }}

pnpx vercel build
if [ "${{ inputs.production-staged }}" = "true" ]; then
unset PUBLIC_COLLAB_RELAY_URL
pnpx vercel build --prod --token "${{ inputs.vercel-token }}"
else
export PUBLIC_COLLAB_RELAY_URL=${{ inputs.PUBLIC_COLLAB_RELAY_URL }}
pnpx vercel build --token "${{ inputs.vercel-token }}"
fi
shell: bash

- name: Patch
Expand All @@ -108,9 +117,17 @@ runs:
- name: Deploy
id: deploy
run: |
deploy_args=(
--prebuilt
--token "${{ inputs.vercel-token }}"
)

if [ "${{ inputs.production-staged }}" = "true" ]; then
deploy_args+=(--prod --skip-domain)
fi

pnpx vercel deploy \
--prebuilt \
--token ${{ inputs.vercel-token }} \
"${deploy_args[@]}" \
2> >(tee info.txt >&2) | tee domain.txt

echo "domain=$(cat ./domain.txt)" >> $GITHUB_OUTPUT
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/vercel-deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ jobs:
ref-name: ${{ github.event_name == 'pull_request_target' && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}
sha: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}
environment: ${{ matrix.environment }}
production-staged: ${{ github.event_name == 'push' && matrix.environment == 'staging' && startsWith(github.ref_name, 'release-') && endsWith(github.ref_name, '.staging') }}
PUBLIC_COLLAB_RELAY_URL: ${{ vars.PUBLIC_COLLAB_RELAY_URL }}

- name: Debug Vercel Outputs
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/builder/builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import type { TokenPermissions } from "@webstudio-is/authorization-token";
import { useToastErrors } from "~/shared/error/toast-error";
import { initBuilderApi } from "~/shared/builder-api";
import { updateWebstudioData } from "~/shared/instance-utils";
import { migrateWebstudioDataMutable } from "@webstudio-is/sdk/migrations/webstudio-data";
import { migrateWebstudioDataMutable } from "@webstudio-is/project-migrations";
import { Loading, LoadingBackground } from "./shared/loading";
import { mergeRefs } from "@react-aria/utils";
import { CommandPanel } from "./features/command-panel";
Expand Down
6 changes: 4 additions & 2 deletions apps/builder/app/builder/features/marketplace/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
getStyleDeclKey,
migratePages,
type Asset,
type SerializedPages,
type WebstudioData,
} from "@webstudio-is/sdk";
import {
migratePages,
type SerializedPages,
} from "@webstudio-is/project-migrations/pages";
import type { CompactBuild } from "@webstudio-is/project-build";

const getPair = <Item extends { id: string }>(item: Item) =>
Expand Down
38 changes: 38 additions & 0 deletions apps/builder/app/routes/rest.$.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import {
ApiCompatibilityTarget,
apiClientHeader,
createApiCompatibilityPayload,
} from "@webstudio-is/trpc-interface/api-compatibility";

const getTarget = (request: Request) => {
const client = ApiCompatibilityTarget.safeParse(
request.headers.get(apiClientHeader)
);
if (client.success) {
return client.data;
}

return "browser";
};

const createResponse = (request: Request) => {
const payload = createApiCompatibilityPayload({
reason: "apiRouteNotFound",
target: getTarget(request),
});

return new Response(JSON.stringify({ success: false, error: payload }), {
status: 426,
headers: {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "no-store",
},
});
};

export const loader = ({ request }: LoaderFunctionArgs) => {
return createResponse(request);
};

export const action = loader;
2 changes: 1 addition & 1 deletion apps/builder/app/routes/rest.data.$projectId.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import * as projectApi from "@webstudio-is/project/index.server";
import { loadDevBuildByProjectId } from "@webstudio-is/project-build/index.server";
import { serializePages } from "@webstudio-is/sdk";
import { serializePages } from "@webstudio-is/project-migrations/pages";
import { loadAssetsByProject } from "@webstudio-is/asset-uploader/index.server";
import { createContext } from "~/shared/context.server";
import { preventCrossOriginCookie } from "~/services/no-cross-origin-cookie";
Expand Down
5 changes: 4 additions & 1 deletion apps/builder/app/services/auth-strategy/ws.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ const asyncLocalStorage = new AsyncLocalStorage<
>();

// remix-auth-oauth2 logs OAuth state, PKCE verifier, and full callback URLs.
createDebugRaw.enable(`${createDebugRaw.disable()},-OAuth2Strategy`);
// Match both the exact namespace and any prefixed namespace enabled by DEBUG=*.
createDebugRaw.enable(
`${createDebugRaw.disable()},-OAuth2Strategy,-*OAuth2Strategy*`
);

/**
* The main issue with OAuth2Strategy is that it forces us to define authorizationEndpoint, tokenEndpoint, and redirectURI
Expand Down
1 change: 1 addition & 0 deletions apps/builder/app/services/trpc.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const trpcSharedClient = createTrpcProxyServiceClient(
url: TRPC_SERVER_URL,
token: TRPC_SERVER_API_TOKEN,
branchName: GITHUB_REF_NAME,
clientVersion: staticEnv.GITHUB_SHA ?? "local",
}
: undefined
);
7 changes: 2 additions & 5 deletions apps/builder/app/shared/builder-data.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {
getStyleDeclKey,
migratePages,
type WebstudioData,
} from "@webstudio-is/sdk";
import { getStyleDeclKey, type WebstudioData } from "@webstudio-is/sdk";
import { migratePages } from "@webstudio-is/project-migrations/pages";
import type { MarketplaceProduct } from "@webstudio-is/project-build";
import type { Project } from "@webstudio-is/project";
import type { loader } from "~/routes/rest.data.$projectId";
Expand Down
23 changes: 23 additions & 0 deletions apps/builder/app/shared/context.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import { readLoginSessionBloomFilter } from "~/services/session.server";
import type { BloomFilter } from "~/services/bloom-filter.server";
import { isBuilder, isCanvas } from "./router-utils";
import { parseBuilderUrl } from "@webstudio-is/http-client";
import {
ApiClient,
apiClientHeader,
apiClientVersionHeader,
} from "@webstudio-is/trpc-interface/api-compatibility";

export const extractAuthFromRequest = async (request: Request) => {
if (isCanvas(request)) {
Expand Down Expand Up @@ -180,6 +185,21 @@ const createTrpcCache = () => {
};
};

const createApiClientContext = (request: Request): AppContext["apiClient"] => {
const client = ApiClient.safeParse(request.headers.get(apiClientHeader));
if (client.success === false) {
return {
type: "unknown",
version: undefined,
};
}

return {
type: client.data,
version: request.headers.get(apiClientVersionHeader) ?? undefined,
};
};

export const createPostgrestContext = () => {
return { client: createClient(env.POSTGREST_URL, env.POSTGREST_API_KEY) };
};
Expand Down Expand Up @@ -212,6 +232,7 @@ export const createContext = async (request: Request): Promise<AppContext> => {
const entri = createEntriContext();
const { planFeatures, purchases } = await resolvePlanInfo(authorization);
const trpcCache = createTrpcCache();
const apiClient = createApiClientContext(request);

const getOwnerPlanFeatures = async (userId: string) => {
const results = await getPlanInfo([userId], { postgrest });
Expand All @@ -233,6 +254,7 @@ export const createContext = async (request: Request): Promise<AppContext> => {
entri,
planFeatures,
purchases,
apiClient,
trpcCache,
postgrest,
createTokenContext,
Expand All @@ -247,6 +269,7 @@ export const createContext = async (request: Request): Promise<AppContext> => {
entri,
planFeatures,
purchases,
apiClient,
trpcCache,
postgrest,
createTokenContext,
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/shared/db/canvas.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
findPageByIdOrPath,
getAllPages,
getStyleDeclKey,
serializePages,
} from "@webstudio-is/sdk";
import { serializePages } from "@webstudio-is/project-migrations/pages";
import * as projectApi from "@webstudio-is/project/index.server";
import { getUserById, type User } from "./user.server";

Expand Down
34 changes: 32 additions & 2 deletions apps/builder/app/shared/fetch.client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { toast } from "@webstudio-is/design-system";
import {
apiClientHeader,
apiClientVersionHeader,
getApiCompatibilityPayload,
} from "@webstudio-is/trpc-interface/api-compatibility";
import { csrfToken } from "./csrf.client";
import { $authToken } from "./nano-states";
import { publicStaticEnv } from "~/env/env.static";

/**
* To avoid fetch interception from the canvas, i.e., `globalThis.fetch = () => console.log('INTERCEPTED');`,
Expand All @@ -11,7 +17,10 @@ const _fetch = globalThis.fetch;
* To avoid fetch interception from the canvas, i.e., `globalThis.fetch = () => console.log('INTERCEPTED');`,
* To add csrf token to the headers.
*/
export const fetch: typeof globalThis.fetch = (requestInfo, requestInit) => {
export const fetch: typeof globalThis.fetch = async (
requestInfo,
requestInit
) => {
if (csrfToken === undefined) {
toast.error("CSRF token is not set.");
throw new Error("CSRF token is not set.");
Expand All @@ -20,6 +29,8 @@ export const fetch: typeof globalThis.fetch = (requestInfo, requestInit) => {
const headers = new Headers(requestInit?.headers);

headers.set("X-CSRF-Token", csrfToken);
headers.set(apiClientHeader, "browser");
headers.set(apiClientVersionHeader, publicStaticEnv.VERSION);

const authToken = $authToken.get();

Expand All @@ -34,5 +45,24 @@ export const fetch: typeof globalThis.fetch = (requestInfo, requestInit) => {
headers,
};

return _fetch(requestInfo, modifiedInit);
const response = await _fetch(requestInfo, modifiedInit);
if (response.ok === false) {
const contentType = response.headers.get("Content-Type") ?? "";
if (contentType.includes("application/json")) {
const body: unknown = await response
.clone()
.json()
.catch(() => undefined);
const payload = getApiCompatibilityPayload(body);
if (payload?.action.type === "reloadBrowser") {
toast.error(payload.message, {
id: "api-compatibility",
duration: Number.POSITIVE_INFINITY,
});
throw new Error(payload.message, { cause: payload });
}
}
}

return response;
};
2 changes: 1 addition & 1 deletion apps/builder/app/shared/marketplace/db.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import type { AppContext } from "@webstudio-is/trpc-interface/index.server";
import type { Project } from "@webstudio-is/project";
import { loadAssetsByProject } from "@webstudio-is/asset-uploader/index.server";
import { serializePages } from "@webstudio-is/sdk";
import { serializePages } from "@webstudio-is/project-migrations/pages";

export const getBuildProdData = async (
{ projectId }: { projectId: Project["id"] },
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/shared/page-utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
ROOT_INSTANCE_ID,
encodeDataVariableId,
getHomePage,
migratePages,
type Instance,
type WebstudioData,
} from "@webstudio-is/sdk";
import { migratePages } from "@webstudio-is/project-migrations/pages";
import {
createDefaultPages,
createRootFolder,
Expand Down
7 changes: 7 additions & 0 deletions apps/builder/app/shared/trpc/trpc-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AppRouter } from "~/services/trcp-router.server";
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import { getApiCompatibilityPayload } from "@webstudio-is/trpc-interface/api-compatibility";

import type {
AnyMutationProcedure,
Expand Down Expand Up @@ -117,6 +118,12 @@ export const trpcClient: {

console.error("TRPC ERROR", error);

const payload = getApiCompatibilityPayload(error);
if (payload?.action.type === "reloadBrowser") {
setError(payload.message);
return;
}

if (error instanceof Error) {
setError(error.message);
return;
Expand Down
3 changes: 2 additions & 1 deletion apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"prebuild": "which remix",
"build": "remix vite:build",
"css-to-ws": "NODE_OPTIONS=--conditions=webstudio css-to-ws",
"css-to-ws": "tsx --conditions=webstudio ../../packages/css-data/bin/css-to-ws.ts",
"build:webflow-presets": "pnpm css-to-ws ./app/shared/copy-paste/plugin-webflow/style-presets.css ./app/shared/copy-paste/plugin-webflow/__generated__/style-presets.ts",
"build:tailwind-preflight": "tsx --conditions=webstudio ./app/shared/tailwind/preflight-bin.ts && prettier --write ./app/shared/tailwind/__generated__/preflight.ts",
"start": "remix-serve build/server/index.js",
Expand Down Expand Up @@ -75,6 +75,7 @@
"@webstudio-is/postgrest": "workspace:*",
"@webstudio-is/project": "workspace:*",
"@webstudio-is/project-build": "workspace:*",
"@webstudio-is/project-migrations": "workspace:*",
"@webstudio-is/react-sdk": "workspace:*",
"@webstudio-is/sdk": "workspace:*",
"@webstudio-is/sdk-components-animation": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"@clack/prompts": "^0.10.0",
"@emotion/hash": "^0.9.2",
"@trpc/client": "^10.45.2",
"@webstudio-is/project-migrations": "workspace:*",
"@webstudio-is/trpc-interface": "workspace:*",
"acorn": "^8.14.1",
"acorn-walk": "^8.3.4",
"change-case": "^5.4.4",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { initFlow } from "./commands/init-flow";
import makeCLI from "yargs";
import packageJson from "../package.json" assert { type: "json" };
import type { CommonYargsArgv } from "./commands/yargs-types";
import { isHandledCliError } from "./errors";

export const main = async () => {
try {
Expand Down Expand Up @@ -61,6 +62,9 @@ export const main = async () => {

await cmd.parse();
} catch (error) {
if (isHandledCliError(error)) {
exit(1);
}
console.error(error);
exit(1);
}
Expand Down
Loading
Loading