Skip to content

Commit

Permalink
[ENG-1090] Add script to bundle typescript SDK that can dynamically i…
Browse files Browse the repository at this point in the history
…mported into a web page (konfig-dev#135)

* testing bundled SDK works

* regenerate openapi for rce

* add proxy to nextjs

* add postgres/data to .gitignore

* add postgres instructions

* add prisma migrate dev instructions

* add webpack template for TypeScript SDK

* add yarn.lock to files to hard-coded files to not delete

* add webpack + remove yarn.lock template + fix trailing slash bug + make requestAfterHook async

* add changeset

* hard-code client variable

* update snaptrade-sdks
  • Loading branch information
dphuang2 authored Aug 26, 2023
1 parent 7f4ce23 commit c1e90ed
Show file tree
Hide file tree
Showing 29 changed files with 2,007 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ tags
# Persistent undo
[._]*.un~

postgres/data

# End of https://www.toptal.com/developers/gitignore/api/vim,macos
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ git submodule update --init --recursive --remote --merge
## Environment Setup

1. Run Postgres as a background process

```shell
# in /konfig
brew install postgresql
mkdir -p postgres/data
initdb -D ./postgres/data
pg_ctl -D ./postgres/data start
```

1. Setup `.env` file in `generator/konfig-dash` to something like:

```
Expand Down Expand Up @@ -50,6 +59,7 @@ Then create `~/.envvars` with values from Dylan.
```shell
cd generator/konfig-dash
yarn # takes some time
yarn rw prisma migrate dev # setup the DB
```
1. Start the server with `yarn dev`

Expand Down
5 changes: 5 additions & 0 deletions generator/konfig-dash/.changeset/silver-flies-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'konfig-cli': minor
---

add yarn.lock to hard-coded list of files to not delete
Original file line number Diff line number Diff line change
Expand Up @@ -1448,7 +1448,11 @@ const DO_NOT_COPY_THESE_FILES = new Set([

const KONFIG_IGNORE_FILE_NAME = '.konfigignore'

const DO_NOT_DELETE_THESE_FILES = new Set(['.git', KONFIG_IGNORE_FILE_NAME])
const DO_NOT_DELETE_THESE_FILES = new Set([
'.git',
KONFIG_IGNORE_FILE_NAME,
'yarn.lock', // for TypeScript SDK
])

const safelyDeleteFiles = async (
directory: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,13 @@ private void addNpmPackageGeneration() {
supportingFiles.add(new SupportingFile("tsconfig.mustache", "", "tsconfig.json"));
supportingFiles.add(new SupportingFile("index.test.mustache", "", "index.test.ts"));
supportingFiles.add(new SupportingFile("tsconfig.test.mustache", "", "tsconfig.test.json"));
supportingFiles.add(new SupportingFile("yarn.mustache", "", "yarn.lock"));
supportingFiles.add(new SupportingFile("webpack.mustache", "", "webpack.config.js"));

// Dylan: I tried maintaining a yarn.lock template but its too cumbersome to keep up to date
// So I think we should just generate a yarn.lock file from the package.json file using yarn install
// and then just check that in.
// supportingFiles.add(new SupportingFile("yarn.mustache", "", "yarn.lock"));

supportingFiles.add(new SupportingFile("jest.config.mustache", "", "jest.config.ts"));
// in case ECMAScript 6 is supported add another tsconfig for an ESM (ECMAScript Module)
if (supportsES6) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,14 @@ export const serializeDataIfNeeded = function (value: any, requestOptions: any,
* @export
*/
export const toPathString = function (url: URL) {
return url.pathname + url.search + url.hash
return removeTrailingSlash(url.pathname) + url.search + url.hash
}

/**
* remove trailing slash from string
*/
export const removeTrailingSlash = function (url: string) {
return url.replace(/\/$/, "");
}

/**
Expand All @@ -186,7 +193,7 @@ export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxi
return async <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
requestBeforeUrlHook({axiosArgs, basePath, configuration})
const url = (configuration?.basePath || basePath) + axiosArgs.url
requestAfterHook({axiosArgs, basePath, url, configuration})
await requestAfterHook({axiosArgs, basePath, url, configuration})
try {
return await axios.request<T, R>({ ...axiosArgs.options, url });
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"sideEffects": false,
{{/supportsES6}}
"scripts": {
"build": "npm run clean && tsc{{#supportsES6}} && tsc -p tsconfig.esm.json{{/supportsES6}}",
"build": "npm run clean && tsc && webpack{{#supportsES6}} && tsc -p tsconfig.esm.json{{/supportsES6}}",
"clean": "rm -rf dist/",
"prepack": "npm run build",
"test": "jest"
Expand All @@ -42,8 +42,11 @@
"@types/node": "^12.11.5",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"ts-loader": "^9.4.4",
"ts-node": "^10.9.1",
"typescript": "^4.0"
"typescript": "^4.0",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
}{{#npmRepository}},{{/npmRepository}}
{{#npmRepository}}
"publishConfig": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { RequestArgs } from "./base";
import { Configuration } from "./configuration";

export function requestAfterHook(request: {
export async function requestAfterHook(request: {
axiosArgs: RequestArgs;
basePath: string;
url: string;
configuration?: Configuration;
}): void {}
}): Promise<void> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const path = require("path");

module.exports = {
mode: "production",
entry: "./index.ts",
output: {
filename: "browser.js",
path: path.resolve(__dirname, "dist"),
library: "client",
libraryTarget: "umd",
},
resolve: {
extensions: [".ts", ".js"],
fallback: {
crypto: false,
},
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
};
93 changes: 93 additions & 0 deletions generator/konfig-next-app/src/pages/api/proxy/[[...path]].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { NextApiRequest, NextApiResponse } from "next";
import axios from "axios";
import { URL } from "url";

// CORS middleware
function handleCors(req: NextApiRequest, res: NextApiResponse) {
const allowedOrigin =
process.env.NODE_ENV === "production" ? "https://demo.konfigthis.com" : "*";

const requestHeaders = req.headers["access-control-request-headers"];

res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", requestHeaders || "*");

if (req.method === "OPTIONS") {
// Preflight request. Reply successfully:
res.status(204).send("");
return false;
}

return true;
}

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (!handleCors(req, res)) return; // handle preflight CORS requests

const targetUrl = req.headers["x-proxy-target"];

if (!targetUrl || typeof targetUrl !== "string") {
return res
.status(400)
.json({ error: "Missing or invalid x-proxy-target header" });
}

// Construct the full URL including any subpaths and query parameters
const url = new URL(targetUrl);
const extraPath = ((req.query.path as string[]) || []).join("/"); // Added check for undefined
if (extraPath) {
url.pathname += "/" + extraPath;
}

// Append any query parameters
if (req.url) {
url.search = req.url.split("?")[1] || "";
}

// Fixes: Hostname/IP does not match certificate's altnames: Host: localhost.
// is not in the cert's altnames: DNS:*.passiv.com, DNS:passiv.com"
delete req.headers["host"];

// remove other headers that should be generated when making request by axios
// delete req.headers["accept-encoding"];
// delete req.headers["content-length"];
// delete req.headers["transfer-encoding"];
// delete req.headers["content-type"];

try {
const response = await axios({
method: req.method as any,
url: url.toString(),
headers: req.headers,
data: req.body,
validateStatus: () => true, // ensure all responses are forwarded, not just successful ones
});

// Forward status code
res.status(response.status);

// Forward headers
for (const [key, value] of Object.entries(response.headers)) {
// skip content-length
if (key === "content-length") continue;
if (key === "transfer-encoding") continue;

res.setHeader(key, value as any);
}

// Forward response
res.send(response.data);
} catch (error) {
if (error instanceof Error) {
return res.status(500).json({
error: `Fetching from the target URL failed with message: ${error.message}`,
});
} else {
return res.status(500).json({ error: "An unknown error occurred." });
}
}
}
16 changes: 16 additions & 0 deletions generator/konfig-python-remote-code-executor/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,22 @@
}
}
}
},
"/healthz": {
"get": {
"summary": "Health Check",
"operationId": "health_check_healthz_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"components": {
Expand Down
18 changes: 18 additions & 0 deletions misc/test-bundled-typescript-sdk/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
24 changes: 24 additions & 0 deletions misc/test-bundled-typescript-sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
27 changes: 27 additions & 0 deletions misc/test-bundled-typescript-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
```

- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
14 changes: 14 additions & 0 deletions misc/test-bundled-typescript-sdk/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/browser.js"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
28 changes: 28 additions & 0 deletions misc/test-bundled-typescript-sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "test-bundled-typescript-sdk",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}
1 change: 1 addition & 0 deletions misc/test-bundled-typescript-sdk/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c1e90ed

Please sign in to comment.