diff --git a/.github/actions/publish/action.yaml b/.github/actions/publish/action.yaml index 930f3578c2..1494af57e9 100644 --- a/.github/actions/publish/action.yaml +++ b/.github/actions/publish/action.yaml @@ -35,4 +35,4 @@ runs: run: npm publish -w ${{ inputs.workdir }} --access public shell: bash env: - NODE_AUTH_TOKEN: ${{ inputs.token }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ inputs.token }} diff --git a/.github/workflows/http-client.yml b/.github/workflows/http-client.yml new file mode 100644 index 0000000000..30e13efc67 --- /dev/null +++ b/.github/workflows/http-client.yml @@ -0,0 +1,19 @@ +name: HTTP Client +on: + push: + paths: + - "packages/http-client/**/*" + - ".github/workflows/http-client.yml" + - ".github/actions/publish/action.yaml" + branches: + - main +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/publish + with: + workdir: ./packages/http-client + filter: "@webstudio-is/http-client" + token: ${{ secrets.NPM_TOKEN }} diff --git a/packages/http-client/LICENSE b/packages/http-client/LICENSE new file mode 100644 index 0000000000..1f6edad9a7 --- /dev/null +++ b/packages/http-client/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Webstudio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/http-client/README.md b/packages/http-client/README.md new file mode 100644 index 0000000000..7d9c4877ad --- /dev/null +++ b/packages/http-client/README.md @@ -0,0 +1,3 @@ +# Webstudio HTTP Client + +Enables to optimize the http calls to Webstudio API. diff --git a/packages/http-client/jest.config.js b/packages/http-client/jest.config.js new file mode 100644 index 0000000000..4eeecbaf4e --- /dev/null +++ b/packages/http-client/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@webstudio-is/jest-config"); diff --git a/packages/http-client/package.json b/packages/http-client/package.json new file mode 100644 index 0000000000..bb0e953424 --- /dev/null +++ b/packages/http-client/package.json @@ -0,0 +1,35 @@ +{ + "name": "@webstudio-is/http-client", + "version": "0.0.1", + "description": "Webstudio HTTP Client", + "author": "Webstudio ", + "homepage": "https://webstudio.is", + "scripts": { + "dev": "tsup src/index.ts --format esm,cjs --watch --out-dir=lib", + "test": "jest", + "build": "rm -fr lib && tsc", + "typecheck": "tsc --noEmit", + "lint": "eslint ./src --ext .ts,.tsx --max-warnings 0", + "checks": "yarn typecheck && yarn lint" + }, + "devDependencies": { + "@webstudio-is/jest-config": "*", + "@webstudio-is/react-sdk": "*", + "@webstudio-is/prisma-client": "*", + "tsup": "^6.1.3", + "typescript": "^4.6.2" + }, + "peerDependencies": {}, + "dependencies": { + "node-fetch": "^3.2.10" + }, + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib/*", + "README.md", + "!*.test.*" + ], + "license": "MIT", + "private": true +} diff --git a/packages/http-client/src/fetch-polyfill.ts b/packages/http-client/src/fetch-polyfill.ts new file mode 100644 index 0000000000..a2a7307ea8 --- /dev/null +++ b/packages/http-client/src/fetch-polyfill.ts @@ -0,0 +1,6 @@ +import fetch from "node-fetch"; + +if (globalThis.fetch === undefined) { + // @ts-expect-error node-fetch doesn't implement a complete spec + globalThis.fetch = fetch; +} diff --git a/packages/http-client/src/index.d.ts b/packages/http-client/src/index.d.ts new file mode 100644 index 0000000000..178b0828c3 --- /dev/null +++ b/packages/http-client/src/index.d.ts @@ -0,0 +1,16 @@ +import type { + BaseInstance, + Breakpoint, + UserProp, +} from "@webstudio-is/react-sdk"; + +export type IncludeTypes = "tree" | "props" | "breakpoints"; +export type Includes = { [include in IncludeTypes]: T }; + +export type Project = { + tree: BaseInstance & { errors: string }; + props: Array & { errors: string }; + breakpoints: Array & { + errors: string; + }; +}; diff --git a/packages/http-client/src/index.test.ts b/packages/http-client/src/index.test.ts new file mode 100644 index 0000000000..0381dd59ea --- /dev/null +++ b/packages/http-client/src/index.test.ts @@ -0,0 +1,26 @@ +import { loadProject } from "./index"; + +const existingProject = "ab3619d3-831f-46f8-abb6-609558cffd99"; +const notPublishedProject = "7ec397c6-b3d0-4967-9073-9d83623fcf8e"; +const nonExistingProject = "1"; + +describe("getProjectDetails", () => { + test("loads existing project", async () => { + const response = await loadProject({ projectId: existingProject }); + expect(response.tree.id).toBeTruthy(); + }); + test("loads non-existing project", async () => { + const response = await loadProject({ + projectId: nonExistingProject, + }); + expect(response.tree.errors).toBe("Project required"); + }); + test("loads not published project", async () => { + const response = await loadProject({ + projectId: notPublishedProject, + }); + expect(response.tree.errors).toBe( + "Site needs to be published, production tree ID is null." + ); + }); +}); diff --git a/packages/http-client/src/index.ts b/packages/http-client/src/index.ts new file mode 100644 index 0000000000..80e09ce24f --- /dev/null +++ b/packages/http-client/src/index.ts @@ -0,0 +1,31 @@ +import "./fetch-polyfill"; +import type { Includes, Project } from "./index.d"; + +const protocol = process.env.NODE_ENV === "production" ? "https" : "http"; + +export const loadProject = async ({ + projectId, + host = process.env.DESIGNER_HOST || "localhost:3000", + include = { tree: true, props: true, breakpoints: true }, +}: { + projectId: string; + host?: string; + include?: Includes; +}): Promise => { + const baseUrl = new URL("/", `${protocol}://${host}`); + const treeUrl = new URL(`/rest/tree/${projectId}`, baseUrl); + const propsUrl = new URL(`/rest/props/${projectId}`, baseUrl); + const breakpointsUrl = new URL(`/rest/breakpoints/${projectId}`, baseUrl); + + const [tree, props, breakpoints] = await Promise.all([ + include.tree && (await fetch(treeUrl)).json(), + include.props && (await fetch(propsUrl)).json(), + include.breakpoints && (await fetch(breakpointsUrl)).json(), + ]); + return { + tree, + props, + breakpoints, + }; +}; + diff --git a/packages/http-client/tsconfig.json b/packages/http-client/tsconfig.json new file mode 100644 index 0000000000..eb77362863 --- /dev/null +++ b/packages/http-client/tsconfig.json @@ -0,0 +1,17 @@ +// Extends can't use node's resolution rn https://github.com/dividab/tsconfig-paths/issues/209#issuecomment-1177261091 +{ + "extends": "../../node_modules/@webstudio-is/tsconfig/base.json", + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2020"], + "module": "commonjs", + "jsx": "react-jsx", + "target": "ES2020", + "strict": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "outDir": "./lib" + } +} diff --git a/yarn.lock b/yarn.lock index e1f434483e..d4e9559a1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7767,6 +7767,11 @@ data-uri-to-buffer@^3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -9473,6 +9478,14 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + fetch-retry@^5.0.2: version "5.0.3" resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.3.tgz#edfa3641892995f9afee94f25b168827aa97fe3d" @@ -9709,6 +9722,13 @@ format@^0.2.0: resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -13321,6 +13341,11 @@ node-dir@^0.1.10, node-dir@^0.1.17: dependencies: minimatch "^3.0.2" +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -13333,6 +13358,15 @@ node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^3.2.10: + version "3.2.10" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8" + integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -17026,6 +17060,11 @@ typescript@4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +typescript@^4.6.2: + version "4.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.3.tgz#d59344522c4bc464a65a730ac695007fdb66dd88" + integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== + uglify-js@^3.1.4: version "3.16.2" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.2.tgz#0481e1dbeed343ad1c2ddf3c6d42e89b7a6d4def" @@ -17583,7 +17622,7 @@ web-namespaces@^1.0.0: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== -web-streams-polyfill@^3.1.1: +web-streams-polyfill@^3.0.3, web-streams-polyfill@^3.1.1: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==