webstudio 0.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "webstudio",
3
+ "version": "0.105.0",
4
+ "description": "Webstudio CLI",
5
+ "author": "Webstudio <github@webstudio.is>",
6
+ "homepage": "https://webstudio.is",
7
+ "type": "module",
8
+ "bin": {
9
+ "webstudio-cli": "./bin.js",
10
+ "webstudio": "./bin.js"
11
+ },
12
+ "files": [
13
+ "lib/*",
14
+ "templates/*",
15
+ "bin.js",
16
+ "!*.{test,stories}.*"
17
+ ],
18
+ "license": "AGPL-3.0-or-later",
19
+ "dependencies": {
20
+ "deepmerge": "^4.3.1",
21
+ "env-paths": "^3.0.0",
22
+ "execa": "^7.2.0",
23
+ "ora": "^7.0.1",
24
+ "p-limit": "^4.0.0",
25
+ "picocolors": "^1.0.0",
26
+ "prompts": "^2.4.2",
27
+ "strip-indent": "^4.0.0",
28
+ "title-case": "^4.1.0",
29
+ "yargs": "^17.7.2",
30
+ "zod": "^3.21.4",
31
+ "@webstudio-is/http-client": "0.105.0",
32
+ "@webstudio-is/image": "0.105.0",
33
+ "@webstudio-is/react-sdk": "0.105.0",
34
+ "@webstudio-is/sdk": "0.105.0",
35
+ "@webstudio-is/sdk-components-react": "0.105.0",
36
+ "@webstudio-is/sdk-components-react-radix": "0.105.0",
37
+ "@webstudio-is/sdk-components-react-remix": "0.105.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^18.17.1",
41
+ "@types/prompts": "^2.4.5",
42
+ "tsx": "^3.12.8",
43
+ "typescript": "5.2.2",
44
+ "@webstudio-is/form-handlers": "0.105.0",
45
+ "@webstudio-is/tsconfig": "1.0.7"
46
+ },
47
+ "scripts": {
48
+ "typecheck": "tsc",
49
+ "checks": "pnpm typecheck",
50
+ "build": "rm -rf lib && esbuild src/cli.ts --outdir=lib --bundle --format=esm --packages=external",
51
+ "local-run": "tsx --no-warnings ./src/bin.ts",
52
+ "dev": "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib"
53
+ }
54
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * We use mjs extension as constants in this file is shared with the build script
3
+ * and we use `node --eval` to extract the constants.
4
+ */
5
+ export const assetBaseUrl = "/assets/";
6
+ export const imageBaseUrl = "/assets/";
7
+
8
+ /**
9
+ * @type {import("@webstudio-is/image").ImageLoader}
10
+ */
11
+ export const imageLoader = ({ src }) => {
12
+ return imageBaseUrl + src;
13
+ };
@@ -0,0 +1 @@
1
+ export { Root as default } from "@webstudio-is/react-sdk";
@@ -0,0 +1,34 @@
1
+ {
2
+ "private": true,
3
+ "sideEffects": false,
4
+ "scripts": {
5
+ "build": "remix build",
6
+ "dev": "remix dev",
7
+ "typecheck": "tsc"
8
+ },
9
+ "dependencies": {
10
+ "@remix-run/react": "^1.19.2",
11
+ "@remix-run/server-runtime": "^1.19.2",
12
+ "@remix-run/node": "^1.19.2",
13
+ "@webstudio-is/react-sdk": "^0.105.0",
14
+ "@webstudio-is/sdk-components-react-radix": "^0.105.0",
15
+ "@webstudio-is/sdk-components-react-remix": "^0.105.0",
16
+ "@webstudio-is/sdk-components-react": "^0.105.0",
17
+ "@webstudio-is/form-handlers": "^0.105.0",
18
+ "@webstudio-is/image": "^0.105.0",
19
+ "@webstudio-is/sdk": "^0.105.0",
20
+ "isbot": "^3.6.8",
21
+ "react": "^18.2.0",
22
+ "react-dom": "^18.2.0"
23
+ },
24
+ "devDependencies": {
25
+ "@remix-run/serve": "^1.19.2",
26
+ "@remix-run/dev": "^1.19.2",
27
+ "@types/react": "^18.2.20",
28
+ "@types/react-dom": "^18.2.7",
29
+ "typescript": "5.2.2"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }
@@ -0,0 +1,18 @@
1
+ /** @type {import('@remix-run/dev').AppConfig} */
2
+ module.exports = {
3
+ ignoredRouteFiles: ["**/.*"],
4
+ serverModuleFormat: "cjs",
5
+ serverDependenciesToBundle: [
6
+ /@webstudio-is\//,
7
+ "nanoid",
8
+ "@jsep-plugin/assignment",
9
+ ],
10
+ future: {
11
+ v2_errorBoundary: true,
12
+ v2_headers: true,
13
+ v2_meta: true,
14
+ v2_normalizeFormMethod: true,
15
+ v2_routeConvention: true,
16
+ v2_dev: true,
17
+ },
18
+ };
@@ -0,0 +1,2 @@
1
+ /// <reference types="@remix-run/dev" />
2
+ /// <reference types="@remix-run/node" />
@@ -0,0 +1,26 @@
1
+ {
2
+ "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mjs"],
3
+ "compilerOptions": {
4
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
5
+ "isolatedModules": true,
6
+ "esModuleInterop": true,
7
+ "jsx": "react-jsx",
8
+ "moduleResolution": "bundler",
9
+ "resolveJsonModule": true,
10
+ "target": "ES2022",
11
+ "strict": true,
12
+ "allowJs": true,
13
+ "checkJs": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "allowImportingTsExtensions": true,
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "~/*": ["./app/*"]
19
+ },
20
+ "customConditions": ["webstudio"],
21
+
22
+ // Remix takes care of building everything in `remix build`.
23
+ "noEmit": true,
24
+ "skipLibCheck": true
25
+ }
26
+ }
@@ -0,0 +1,21 @@
1
+ import type { EntryContext } from "@remix-run/node";
2
+ import { RemixServer } from "@remix-run/react";
3
+ import { renderToString } from "react-dom/server";
4
+
5
+ export default function handleRequest(
6
+ request: Request,
7
+ responseStatusCode: number,
8
+ responseHeaders: Headers,
9
+ remixContext: EntryContext
10
+ ) {
11
+ const markup = renderToString(
12
+ <RemixServer context={remixContext} url={request.url} />
13
+ );
14
+
15
+ responseHeaders.set("Content-Type", "text/html");
16
+
17
+ return new Response("<!DOCTYPE html>" + markup, {
18
+ headers: responseHeaders,
19
+ status: responseStatusCode,
20
+ });
21
+ }
@@ -0,0 +1,7 @@
1
+ [build]
2
+ command = "npm run build"
3
+ publish = "public"
4
+
5
+ [dev]
6
+ command = "npm run dev"
7
+ targetPort = 3000
@@ -0,0 +1,11 @@
1
+ {
2
+ "scripts": {
3
+ "predev": "node -e \"fs.rmSync('./public/_redirects', { recursive: true, force: true })\"",
4
+ "start": "netlify serve"
5
+ },
6
+ "dependencies": {
7
+ "@netlify/functions": "^1.3.0",
8
+ "@netlify/edge-functions": "^2.0.0",
9
+ "@netlify/remix-edge-adapter": "1.2.0"
10
+ }
11
+ }
@@ -0,0 +1,27 @@
1
+ const { config } = require("@netlify/remix-edge-adapter");
2
+ const baseConfig =
3
+ process.env.NODE_ENV === "production"
4
+ ? config
5
+ : {
6
+ ignoredRouteFiles: ["**/.*"],
7
+ serverModuleFormat: "cjs",
8
+ serverDependenciesToBundle: [
9
+ /@webstudio-is\//,
10
+ "nanoid",
11
+ "@jsep-plugin/assignment",
12
+ ],
13
+ };
14
+
15
+ /** @type {import('@remix-run/dev').AppConfig} */
16
+ module.exports = {
17
+ ignoredRouteFiles: ["**/.*"],
18
+ ...baseConfig,
19
+ future: {
20
+ v2_errorBoundary: true,
21
+ v2_headers: true,
22
+ v2_meta: true,
23
+ v2_normalizeFormMethod: true,
24
+ v2_routeConvention: true,
25
+ v2_dev: true,
26
+ },
27
+ };
@@ -0,0 +1,19 @@
1
+ // Import path interpreted by the Remix compiler
2
+ import * as build from "@remix-run/dev/server-build";
3
+ import { createRequestHandler } from "@netlify/remix-edge-adapter";
4
+
5
+ export default createRequestHandler({
6
+ build,
7
+ // process.env.NODE_ENV is provided by Remix at compile time
8
+ mode: process.env.NODE_ENV,
9
+ });
10
+
11
+ export const config = {
12
+ cache: "manual",
13
+ path: "/*",
14
+ // Let the CDN handle requests for static assets, i.e. ^/_assets/*$
15
+ //
16
+ // Add other exclusions here, e.g. "^/api/*$" for custom Netlify functions or
17
+ // custom Netlify Edge Functions
18
+ excluded_patterns: ["^/_assets/*$"],
19
+ };
@@ -0,0 +1,7 @@
1
+ # This template uses this file instead of the typicial Netlify _redirects file.
2
+ # For more information about redirects and rewrites, see https://docs.netlify.com/routing/redirects/.
3
+
4
+ # Do not remove the line below. This is required to serve the site when deployed.
5
+ /* /.netlify/functions/server 200
6
+
7
+ # Add other redirects and rewrites here and/or in your netlify.toml
@@ -0,0 +1,21 @@
1
+ import type { EntryContext } from "@remix-run/node";
2
+ import { RemixServer } from "@remix-run/react";
3
+ import { renderToString } from "react-dom/server";
4
+
5
+ export default function handleRequest(
6
+ request: Request,
7
+ responseStatusCode: number,
8
+ responseHeaders: Headers,
9
+ remixContext: EntryContext
10
+ ) {
11
+ const markup = renderToString(
12
+ <RemixServer context={remixContext} url={request.url} />
13
+ );
14
+
15
+ responseHeaders.set("Content-Type", "text/html");
16
+
17
+ return new Response("<!DOCTYPE html>" + markup, {
18
+ headers: responseHeaders,
19
+ status: responseStatusCode,
20
+ });
21
+ }
@@ -0,0 +1,14 @@
1
+ [build]
2
+ command = "remix build && cp _app_redirects public/_redirects"
3
+ publish = "public"
4
+
5
+ [dev]
6
+ command = "npm run dev"
7
+ targetPort = 3000
8
+
9
+ [[headers]]
10
+ for = "/build/*"
11
+
12
+ [headers.values]
13
+ # Set to 60 seconds as an example. You can also add cache headers via Remix. See the documentation on [headers](https://remix.run/docs/en/v1/route/headers) in Remix.
14
+ "Cache-Control" = "public, max-age=60, s-maxage=60"
@@ -0,0 +1,10 @@
1
+ {
2
+ "scripts": {
3
+ "predev": "node -e \"fs.rmSync('./public/_redirects', { recursive: true, force: true })\"",
4
+ "start": "netlify serve"
5
+ },
6
+ "dependencies": {
7
+ "@netlify/functions": "^1.3.0",
8
+ "@netlify/remix-adapter": "^1.0.0"
9
+ }
10
+ }
@@ -0,0 +1,29 @@
1
+ const baseConfig =
2
+ process.env.NODE_ENV === "production"
3
+ ? // when running the Netify CLI or building on Netlify, we want to use
4
+ {
5
+ server: "./server.js",
6
+ serverBuildPath: ".netlify/functions-internal/server.js",
7
+ }
8
+ : // otherwise support running remix dev, i.e. no custom server
9
+ undefined;
10
+
11
+ /** @type {import('@remix-run/dev').AppConfig} */
12
+ module.exports = {
13
+ ...baseConfig,
14
+ ignoredRouteFiles: ["**/.*"],
15
+ serverModuleFormat: "cjs",
16
+ serverDependenciesToBundle: [
17
+ /@webstudio-is\//,
18
+ "nanoid",
19
+ "@jsep-plugin/assignment",
20
+ ],
21
+ future: {
22
+ v2_errorBoundary: true,
23
+ v2_headers: true,
24
+ v2_meta: true,
25
+ v2_normalizeFormMethod: true,
26
+ v2_routeConvention: true,
27
+ v2_dev: true,
28
+ },
29
+ };
@@ -0,0 +1,7 @@
1
+ import { createRequestHandler } from "@netlify/remix-adapter";
2
+ import * as build from "@remix-run/dev/server-build";
3
+
4
+ export const handler = createRequestHandler({
5
+ build,
6
+ mode: process.env.NODE_ENV,
7
+ });
@@ -0,0 +1,178 @@
1
+ /* eslint-disable camelcase */
2
+ import {
3
+ type V2_ServerRuntimeMetaFunction,
4
+ type LinksFunction,
5
+ type LinkDescriptor,
6
+ type ActionArgs,
7
+ json,
8
+ } from "@remix-run/server-runtime";
9
+ import type { Page as PageType } from "@webstudio-is/sdk";
10
+ import { ReactSdkContext } from "@webstudio-is/react-sdk";
11
+ import { n8nHandler, getFormId } from "@webstudio-is/form-handlers";
12
+ import { Scripts, ScrollRestoration } from "@remix-run/react";
13
+ import {
14
+ fontAssets,
15
+ pageData,
16
+ user,
17
+ projectId,
18
+ pagesPaths,
19
+ formsProperties,
20
+ Page,
21
+ } from "../__generated__/index";
22
+ import css from "../__generated__/index.css";
23
+ import { assetBaseUrl, imageBaseUrl, imageLoader } from "~/constants.mjs";
24
+
25
+ export type PageData = {
26
+ page: PageType;
27
+ };
28
+
29
+ export const meta: V2_ServerRuntimeMetaFunction = () => {
30
+ const { page } = pageData;
31
+ const metas: ReturnType<V2_ServerRuntimeMetaFunction> = [
32
+ { title: page?.title || "Webstudio" },
33
+ ];
34
+ for (const [name, value] of Object.entries(page?.meta ?? {})) {
35
+ if (name.startsWith("og:")) {
36
+ metas.push({
37
+ property: name,
38
+ content: value,
39
+ });
40
+ continue;
41
+ }
42
+
43
+ metas.push({
44
+ name,
45
+ content: value,
46
+ });
47
+ }
48
+
49
+ return metas;
50
+ };
51
+
52
+ export const links: LinksFunction = () => {
53
+ const result: LinkDescriptor[] = [];
54
+
55
+ result.push({
56
+ rel: "stylesheet",
57
+ href: css,
58
+ });
59
+
60
+ for (const asset of fontAssets) {
61
+ if (asset.type === "font") {
62
+ result.push({
63
+ rel: "preload",
64
+ href: assetBaseUrl + asset.name,
65
+ as: "font",
66
+ crossOrigin: "anonymous",
67
+ // @todo add mimeType
68
+ // type: asset.mimeType,
69
+ });
70
+ }
71
+ }
72
+
73
+ return result;
74
+ };
75
+
76
+ const getRequestHost = (request: Request): string =>
77
+ request.headers.get("x-forwarded-host") || request.headers.get("host") || "";
78
+
79
+ const getMethod = (value: string | undefined) => {
80
+ if (value === undefined) {
81
+ return "post";
82
+ }
83
+
84
+ switch (value.toLowerCase()) {
85
+ case "get":
86
+ return "get";
87
+ default:
88
+ return "post";
89
+ }
90
+ };
91
+
92
+ export const action = async ({ request, context }: ActionArgs) => {
93
+ const formData = await request.formData();
94
+
95
+ const formId = getFormId(formData);
96
+ if (formId === undefined) {
97
+ // We're throwing rather than returning { success: false }
98
+ // because this isn't supposed to happen normally: bug or malicious user
99
+ throw json("Form not found", { status: 404 });
100
+ }
101
+
102
+ const formProperties = formsProperties.get(formId);
103
+
104
+ // form properties are not defined when defaults are used
105
+ const { action, method } = formProperties ?? {};
106
+
107
+ const email = user?.email;
108
+
109
+ if (email == null) {
110
+ return { success: false };
111
+ }
112
+
113
+ // wrapped in try/catch just in cases new URL() throws
114
+ // (should not happen)
115
+ let pageUrl: URL;
116
+ try {
117
+ pageUrl = new URL(request.url);
118
+ pageUrl.host = getRequestHost(request);
119
+ } catch {
120
+ return { success: false };
121
+ }
122
+
123
+ if (action !== undefined) {
124
+ try {
125
+ // Test that action is full URL
126
+ new URL(action);
127
+ } catch {
128
+ return json(
129
+ {
130
+ success: false,
131
+ error: "Invalid action URL, must be valid http/https protocol",
132
+ },
133
+ { status: 200 }
134
+ );
135
+ }
136
+ }
137
+
138
+ const formInfo = {
139
+ formData,
140
+ projectId,
141
+ action: action ?? null,
142
+ method: getMethod(method),
143
+ pageUrl: pageUrl.toString(),
144
+ toEmail: email,
145
+ fromEmail: pageUrl.hostname + "@webstudio.email",
146
+ } as const;
147
+
148
+ const result = await n8nHandler({
149
+ formInfo,
150
+ hookUrl: context.N8N_FORM_EMAIL_HOOK as string,
151
+ });
152
+
153
+ return result;
154
+ };
155
+
156
+ const Outlet = () => {
157
+ return (
158
+ <ReactSdkContext.Provider
159
+ value={{
160
+ imageLoader,
161
+ assetBaseUrl,
162
+ imageBaseUrl,
163
+ pagesPaths,
164
+ }}
165
+ >
166
+ <Page
167
+ scripts={
168
+ <>
169
+ <Scripts />
170
+ <ScrollRestoration />
171
+ </>
172
+ }
173
+ />
174
+ </ReactSdkContext.Provider>
175
+ );
176
+ };
177
+
178
+ export default Outlet;
@@ -0,0 +1,8 @@
1
+ node_modules
2
+
3
+ .cache
4
+ .env
5
+ .vercel
6
+ .webstudio
7
+ build
8
+ public/build
@@ -0,0 +1,25 @@
1
+ /**
2
+ * We use mjs extension as constants in this file is shared with the build script
3
+ * and we use `node --eval` to extract the constants.
4
+ */
5
+ export const assetBaseUrl = "/assets/";
6
+ export const imageBaseUrl = "/assets/";
7
+
8
+ /**
9
+ * @type {import("@webstudio-is/image").ImageLoader}
10
+ */
11
+ export const imageLoader = ({ quality, src, width }) => {
12
+ if (process.env.NODE_ENV !== "production") {
13
+ return imageBaseUrl + src;
14
+ }
15
+
16
+ // https://vercel.com/blog/build-your-own-web-framework#automatic-image-optimization
17
+ return (
18
+ "/_vercel/image?url=" +
19
+ encodeURIComponent(imageBaseUrl + src) +
20
+ "&w=" +
21
+ width +
22
+ "&q=" +
23
+ quality
24
+ );
25
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "scripts": {
3
+ "start": "remix-serve build"
4
+ }
5
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "images": {
3
+ "domains": [],
4
+ "sizes": [
5
+ 16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048,
6
+ 3840
7
+ ],
8
+ "minimumCacheTTL": 60,
9
+ "formats": ["image/webp", "image/avif"]
10
+ }
11
+ }