Service workers : Caching

This commit is contained in:
Abhinav Jha 2020-05-05 12:16:51 +00:00
parent cad7c70f61
commit f57cf9d903
24 changed files with 1070 additions and 247 deletions

View File

@ -31,3 +31,5 @@ yarn-error.log*
cypress/videos
cypress/screenshots
results/
/docker/*.pem

View File

@ -2,6 +2,7 @@
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
const merge = require("webpack-merge");
const common = require("./craco.common.config.js");
const WorkboxPlugin = require("workbox-webpack-plugin");
const env = process.env.REACT_APP_ENVIRONMENT;
@ -16,6 +17,14 @@ if (env === "PRODUCTION" || env === "STAGING") {
}),
);
}
plugins.push(
new WorkboxPlugin.InjectManifest({
swSrc: "./src/serviceWorker.js",
mode: "development",
swDest: "./pageService.js",
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
}),
);
module.exports = merge(common, {
webpack: {

View File

@ -6,7 +6,7 @@ const modalWidgetPage = require("../locators/ModalWidget.json");
const widgetsPage = require("../locators/Widgets.json");
Cypress.Commands.add("LogintoApp", (uname, pword) => {
cy.visit("/");
cy.visit("/user/login");
cy.get(loginPage.username).should("be.visible");
cy.get(loginPage.username).type(uname);
cy.get(loginPage.password).type(pword);

View File

@ -0,0 +1,63 @@
server {
listen 80;
server_name dev.appsmith.com;
client_max_body_size 10m;
gzip on;
gzip_proxied any;
proxy_ssl_server_name on;
# sub_filter_once off;
location / {
#proxy_set_header Host $host;
#proxy_set_header X-Real-IP $remote_addr;
# sub_filter ___BASE_URL___ http://$host;
proxy_pass http://localhost:3000;
}
location /api {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
location /oauth2 {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
}
server {
listen 443 ssl http2;
server_name dev.appsmith.com;
ssl_certificate /etc/certificate/dev.appsmith.com.pem;
ssl_certificate_key /etc/certificate/dev.appsmith.com-key.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
gzip on;
proxy_ssl_server_name on;
# sub_filter_once off;
location / {
#proxy_set_header Host $host;
#proxy_set_header X-Real-IP $remote_addr;
# sub_filter ___BASE_URL___ http://$host;
proxy_pass http://localhost:3000;
}
location /api {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
location /oauth2 {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
}

View File

@ -0,0 +1,62 @@
server {
listen 80;
server_name dev.appsmith.com;
client_max_body_size 10m;
gzip on;
gzip_proxied any;
proxy_ssl_server_name on;
# sub_filter_once off;
location / {
#proxy_set_header Host $host;
#proxy_set_header X-Real-IP $remote_addr;
# sub_filter ___BASE_URL___ http://$host;
proxy_pass http://host.docker.internal:3000;
}
location /api {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
location /oauth2 {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
}
server {
listen 443 ssl http2;
server_name dev.appsmith.com;
ssl_certificate /etc/certificate/dev.appsmith.com.pem;
ssl_certificate_key /etc/certificate/dev.appsmith.com-key.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
gzip on;
proxy_ssl_server_name on;
# sub_filter_once off;
location / {
#proxy_set_header Host $host;
#proxy_set_header X-Real-IP $remote_addr;
# sub_filter ___BASE_URL___ http://$host;
proxy_pass http://host.docker.internal:3000;
}
location /api {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
location /oauth2 {
# proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass https://release-api.appsmith.com;
}
}

View File

@ -1,46 +0,0 @@
server {
listen 80;
server_name localhost;
gzip on;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
root /var/www/appsmith;
index index.html index.htm;
location / {
try_files $uri /index.html =404;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/appsmith;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

View File

@ -167,8 +167,9 @@
"redux-devtools-extension": "^2.13.8",
"source-map-explorer": "^2.4.2",
"storybook-addon-designs": "^5.1.1",
"ts-jest": "^24.3.0",
"webpack-merge": "^4.2.2"
"webpack-merge": "^4.2.2",
"workbox-webpack-plugin": "^5.1.2",
"ts-jest": "^24.3.0"
},
"husky": {
"hooks": {

View File

@ -2,18 +2,49 @@
<html lang="en">
<head>
<script rel="prefetch" type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
<meta charset="utf-8" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link href="https://fonts.googleapis.com/css?family=DM+Sans:400,500,700&display=swap" rel="stylesheet" />
<title>Appsmith</title>
<style>
#loader {
position: fixed;
left: 0;
top: 0;
height: 4px;
background:#D36500;
transition: all ease-in 0.3s;
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="loader" style="width: 30vw;"></div>
<div id="root"></div>
<script type="text/javascript">
window.BASE_URL = "___BASE_URL___";
window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("loader").style.width = "50vw";
});
window.addEventListener("load", (event) => {
document.getElementById("loader").style.width = "100vw";
setTimeout(() => {
document.getElementById("loader").style.opacity = 0;
});
});
const registerPageServiceWorker = () => {
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker.register("/pageService.js").catch(error => {
console.log("Service Worker Registration failed: " + error);
});
});
}
};
registerPageServiceWorker();
</script>
<script rel="prefetch" type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
</body>
</html>
</html>

View File

@ -3,7 +3,7 @@
"name": "Appsmith Client Web UI",
"icons": [
{
"src": "favicon.ico",
"src": "favicon-orange.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}

View File

@ -1,11 +1,9 @@
import React from "react";
import { Redirect } from "react-router-dom";
import { useSelector } from "store";
import { APPLICATIONS_URL } from "constants/routes";
export const App = () => {
const currentUser = useSelector(state => state.ui.users.current);
return currentUser ? <Redirect to={APPLICATIONS_URL} /> : null;
return <Redirect to={APPLICATIONS_URL} />;
};
export default App;

View File

@ -58,13 +58,15 @@ axiosInstance.interceptors.response.use(
// console.log(error.response.status);
// console.log(error.response.headers);
if (error.response.status === 401) {
setRouteBeforeLogin(window.location.pathname);
history.push(AUTH_LOGIN_URL);
return Promise.reject({
code: 401,
message: "Unauthorized. Redirecting to login page...",
show: false,
});
if (!/^\/user\/\w+/.test(window.location.pathname)) {
setRouteBeforeLogin(window.location.pathname);
history.push(AUTH_LOGIN_URL);
return Promise.reject({
code: 401,
message: "Unauthorized. Redirecting to login page...",
show: false,
});
}
}
if (error.response.data.responseMeta) {
return Promise.resolve(error.response.data);

View File

@ -11,6 +11,7 @@ export interface FetchPageRequest {
export interface FetchPublishedPageRequest {
pageId: string;
bustCache?: boolean;
}
export interface SavePageRequest {
@ -92,8 +93,9 @@ class PageApi extends Api {
return `v1/layouts/${layoutId}/pages/${pageId}`;
};
static getPublishedPageURL = (pageId: string) => {
return `v1/pages/${pageId}/view`;
static getPublishedPageURL = (pageId: string, bustCache?: boolean) => {
const url = `v1/pages/${pageId}/view`;
return !!bustCache ? url + "?v=" + +new Date() : url;
};
static updatePageUrl = (pageId: string) => `${PageApi.url}/${pageId}`;
@ -120,7 +122,9 @@ class PageApi extends Api {
static fetchPublishedPage(
pageRequest: FetchPublishedPageRequest,
): AxiosPromise<FetchPublishedPageResponse> {
return Api.get(PageApi.getPublishedPageURL(pageRequest.pageId));
return Api.get(
PageApi.getPublishedPageURL(pageRequest.pageId, pageRequest.bustCache),
);
}
static createPage(

View File

@ -198,6 +198,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
CLEAR_PROVIDERS: "CLEAR_PROVIDERS",
BATCHED_UPDATE: "BATCHED_UPDATE",
EXECUTE_BATCH: "EXECUTE_BATCH",
FETCH_ALL_PUBLISHED_PAGES: "FETCH_ALL_PUBLISHED_PAGES",
CREATE_NEW_API_ACTION: "CREATE_NEW_API_ACTION",
SET_CURRENT_CATEGORY: "SET_CURRENT_CATEGORY",
SET_LAST_USED_EDITOR_PAGE: "SET_LAST_USED_EDITOR_PAGE",

View File

@ -42,11 +42,6 @@ div.bp3-popover-arrow {
cursor: default !important;
}
.pace-inactive {
display: none;
}
.display-none {
display: none;
}

View File

@ -7,7 +7,6 @@ import "./index.css";
import { Router, Switch, Redirect } from "react-router-dom";
import history from "./utils/history";
import { ThemeProvider, theme } from "constants/DefaultTheme";
import { appInitializer } from "utils/AppsmithUtils";
import AppRoute from "./pages/common/AppRoute";
import { Slide, ToastContainer } from "react-toastify";

View File

@ -29,14 +29,14 @@ type AppViewerPageContainerProps = {
isFetchingPage: boolean;
widgets?: ContainerWidgetProps<WidgetProps>;
currentPageName?: string;
fetchPage: (pageId: string) => void;
fetchPage: (pageId: string, bustCache?: boolean) => void;
} & RouteComponentProps<AppViewerRouteParams>;
class AppViewerPageContainer extends Component<AppViewerPageContainerProps> {
componentDidMount() {
const { pageId } = this.props.match.params;
if (pageId) {
this.props.fetchPage(pageId);
this.props.fetchPage(pageId, true);
}
}
componentDidUpdate(previously: AppViewerPageContainerProps) {
@ -99,11 +99,12 @@ const mapStateToProps = (state: AppState) => ({
});
const mapDispatchToProps = (dispatch: any) => ({
fetchPage: (pageId: string) =>
fetchPage: (pageId: string, bustCache = false) =>
dispatch({
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT,
payload: {
pageId,
bustCache,
},
}),
});

View File

@ -114,7 +114,7 @@ const ApplicationImage = styled.div`
}
`;
const Control = styled.button<{ fixed?: boolean }>`
const Control = styled.div<{ fixed?: boolean }>`
outline: none;
background: none;
border: none;

View File

@ -1,22 +1,12 @@
import React, { useEffect } from "react";
import { Route } from "react-router-dom";
import { useDispatch } from "react-redux";
import { useSelector } from "store";
import { hasAuthExpired } from "utils/storage";
import { User } from "constants/userConstants";
import { setCurrentUserDetails } from "actions/userActions";
import {
useShowPropertyPane,
useWidgetSelection,
} from "utils/hooks/dragResizeHooks";
import AnalyticsUtil from "utils/AnalyticsUtil";
export const checkAuth = (dispatch: any, currentUser?: User) => {
return hasAuthExpired().then(hasExpired => {
if (!currentUser || hasExpired) {
dispatch(setCurrentUserDetails());
}
});
};
import { setCurrentUserDetails } from "actions/userActions";
export const WrappedComponent = (props: any) => {
const showPropertyPane = useShowPropertyPane();
@ -26,10 +16,7 @@ export const WrappedComponent = (props: any) => {
selectWidget(undefined);
focusWidget(undefined);
const dispatch = useDispatch();
const currentUser = useSelector(state => state.ui.users.current);
checkAuth(dispatch, currentUser);
return currentUser || !props.protected ? props.children : null;
return props.children;
};
const AppRoute = ({
@ -44,6 +31,13 @@ const AppRoute = ({
name: string;
location?: any;
}) => {
const dispatch = useDispatch();
useEffect(() => {
if (!/^\/user\/\w+/.test(rest.location.pathname)) {
dispatch(setCurrentUserDetails());
}
}, [rest.name, rest.location.pathname, dispatch]);
useEffect(() => {
if (!rest.logDisable) {
AnalyticsUtil.logEvent("NAVIGATE_EDITOR", {

View File

@ -66,6 +66,10 @@ export function* initializeAppViewerSaga(
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
]);
yield put({
type: ReduxActionTypes.FETCH_ALL_PUBLISHED_PAGES,
});
yield put({
type: ReduxActionTypes.INITIALIZE_PAGE_VIEWER_SUCCESS,
});

View File

@ -43,7 +43,7 @@ import history from "utils/history";
import { PAGE_LIST_EDITOR_URL } from "constants/routes";
import { extractCurrentDSL } from "utils/WidgetPropsUtils";
import { getEditorConfigs, getWidgets } from "./selectors";
import { getEditorConfigs, getWidgets, getAllPageIds } from "./selectors";
import { validateResponse } from "./ErrorSagas";
import { executePageLoadActions } from "actions/widgetActions";
import { ApiResponse } from "api/ApiResponses";
@ -151,12 +151,13 @@ export function* fetchPageSaga(
}
export function* fetchPublishedPageSaga(
pageRequestAction: ReduxAction<{ pageId: string }>,
pageRequestAction: ReduxAction<{ pageId: string; bustCache: boolean }>,
) {
try {
const { pageId } = pageRequestAction.payload;
const { pageId, bustCache } = pageRequestAction.payload;
const request: FetchPublishedPageRequest = {
pageId,
bustCache,
};
const response: FetchPublishedPageResponse = yield call(
PageApi.fetchPublishedPage,
@ -189,6 +190,19 @@ export function* fetchPublishedPageSaga(
}
}
export function* fetchAllPublishedPagesSaga() {
try {
const pageIds = yield select(getAllPageIds);
yield all(
pageIds.map((pageId: string) => {
return call(PageApi.fetchPublishedPage, { pageId });
}),
);
} catch (error) {
console.log({ error });
}
}
function* savePageSaga() {
const widgets = yield select(getWidgets);
const editorConfigs = yield select(getEditorConfigs) as any;
@ -401,5 +415,9 @@ export default function* pageSagas() {
takeLatest(ReduxActionTypes.DELETE_PAGE_INIT, deletePageSaga),
debounce(500, ReduxActionTypes.SAVE_PAGE_INIT, savePageSaga),
takeLatest(ReduxActionTypes.UPDATE_WIDGET_NAME_INIT, updateWidgetNameSaga),
takeLatest(
ReduxActionTypes.FETCH_ALL_PUBLISHED_PAGES,
fetchAllPublishedPagesSaga,
),
]);
}

View File

@ -99,6 +99,10 @@ export const getWidgetByName = (
);
};
export const getAllPageIds = (state: AppState) => {
return state.entities.pageList.pages.map(page => page.pageId);
};
export const getPluginIdOfPackageName = (
state: AppState,
name: string,

188
app/client/src/serviceWorker.js Executable file → Normal file
View File

@ -1,135 +1,61 @@
// This optional code is used to register a service worker.
// register() is not called by default.
import { precacheAndRoute } from "workbox-precaching";
import { clientsClaim, setCacheNameDetails, skipWaiting } from "workbox-core";
import { registerRoute } from "workbox-routing";
import {
CacheFirst,
NetworkOnly,
StaleWhileRevalidate,
} from "workbox-strategies";
import { ExpirationPlugin } from "workbox-expiration";
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
setCacheNameDetails({
prefix: "appsmith",
suffix: undefined,
precache: "precache-v1",
runtime: "runtime",
googleAnalytics: "appsmith-ga",
});
// To learn more about the benefits of this model and instructions on how to
// opt-in, read http://bit.ly/CRA-PWA
const regexMap = {
appViewPage: new RegExp(/api\/v1\/pages\/\w+\/view$/),
static3PAssets: new RegExp(
/(tiny.cloud|googleapis|gstatic|cloudfront).*.(js|css|woff2)/,
),
shims: new RegExp(/shims\/.*.js/),
profile: new RegExp(/v1\/(users\/profile|organizations)/),
providers: new RegExp(/v1\/marketplace\/(providers|templates)/),
};
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
/* eslint-disable no-restricted-globals */
precacheAndRoute(self.__WB_MANIFEST || []);
self.__WB_DISABLE_DEV_DEBUG_LOGS = false;
skipWaiting();
clientsClaim();
// This route's caching seems too aggressive.
// TODO(abhinav): Figure out if this is really necessary.
// Maybe add the assets locally?
registerRoute(({ url }) => {
return (
regexMap.shims.test(url.pathname) || regexMap.static3PAssets.test(url.href)
);
}, new CacheFirst());
registerRoute(({ url }) => {
return regexMap.profile.test(url.pathname);
}, new NetworkOnly());
registerRoute(({ url }) => {
return regexMap.appViewPage.test(url.pathname);
}, new StaleWhileRevalidate());
registerRoute(
({ url }) => regexMap.providers.test(url.pathname),
new CacheFirst({
plugins: [
new ExpirationPlugin({
maxAgeSeconds: 1 * 60 * 60,
}),
],
}),
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit http://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

52
app/client/start-https.sh Executable file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# run the following commands before the docker run command
# brew install mkcert (if you don't already have it installed)
# run the following commented command from the project root directory
# cd docker && mkcert -install && mkcert "*.appsmith.com" && cd ..
# If this returns a hash successfully, then you can access the application locally using https://dev.appsmith.com
if ! docker_loc="$(type -p "docker")" || [[ -z $docker_loc ]]; then
echo "Could not find docker cli"
exit
fi
KEY_FILE=./docker/_wildcard.appsmith.com-key.pem
CERT_FILE=./docker/_wildcard.appsmith.com.pem
if ! test -f "$KEY_FILE" || ! test -f "$CERT_FILE"; then
echo "
KEY and/or CERTIFICATE not found
Please install mkcert and generate
the key and certificate files
by running the following command
cd docker && mkcert -install && mkcert \"*.appsmith.com\" && cd ..
"
exit
fi
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) machine=Linux
echo "
Starting nginx for Linux...
"
sudo docker run --network host --name wildcard-nginx -d -p 80:80 -p 443:443 -v `pwd`/docker/nginx-linux.conf:/etc/nginx/conf.d/app.conf -v `pwd`/docker/_wildcard.appsmith.com.pem:/etc/certificate/dev.appsmith.com.pem -v `pwd`/docker/_wildcard.appsmith.com-key.pem:/etc/certificate/dev.appsmith.com-key.pem nginx:latest \
&& echo "
nginx is listening on port 443 and forwarding to port 3000
visit https://dev.appsmith.com
"
;;
Darwin*) machine=Mac
echo "
Starting nginx for MacOS...
"
docker run --name wildcard-nginx -d -p 80:80 -p 443:443 -v `pwd`/docker/nginx-mac.conf:/etc/nginx/conf.d/app.conf -v `pwd`/docker/_wildcard.appsmith.com.pem:/etc/certificate/dev.appsmith.com.pem -v `pwd`/docker/_wildcard.appsmith.com-key.pem:/etc/certificate/dev.appsmith.com-key.pem nginx:latest \
&& echo "
nginx is listening on port 443 and forwarding to port 3000
visit https://dev.appsmith.com
"
;;
*) echo "Unknown OS: Please use MacOS or a distribution of linux."
esac

File diff suppressed because it is too large Load Diff