@olenbetong/appframe-vite 3.1.17 → 4.0.0-rc.1

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/lib/build.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { exists } from "fs-extra";
2
+ import { resolve } from "node:path";
1
3
  import { visualizer } from "rollup-plugin-visualizer";
2
4
  import { viteExternalsPlugin } from "vite-plugin-externals";
3
5
  import { importJson } from "./importJson.js";
@@ -17,7 +19,15 @@ export async function addAppframeBuildConfig(config) {
17
19
  "react-dom": "ReactDOM",
18
20
  }));
19
21
  }
22
+ let entry = resolve(process.cwd(), "./src/index.tsx");
23
+ if (!(await exists(resolve(entry)))) {
24
+ entry = resolve(process.cwd(), "./src/index.ts");
25
+ }
26
+ if (!(await exists(resolve(entry)))) {
27
+ entry = resolve(process.cwd(), "./src/index.js");
28
+ }
20
29
  config.build.rollupOptions = {
30
+ input: entry,
21
31
  plugins: [visualizer({ filename: "./dist/stats.html", gzipSize: true })],
22
32
  output: {
23
33
  globals: appframe.build.externals !== false
@@ -1,6 +1,8 @@
1
- import { Application, RequestHandler, Router } from "express";
2
- import { ViteDevServer } from "vite";
3
- export declare function createProxyMiddleware(): Router;
4
- export declare function createHtmlTransformer(): (html: string) => Promise<string>;
5
- export declare function createDevMiddleware(vite: ViteDevServer): RequestHandler;
6
- export declare function startDevServer(app: Application): Promise<void>;
1
+ import { Connect, ViteDevServer } from "vite";
2
+ export declare function getLoginInfo(): Promise<{
3
+ hostname: any;
4
+ username: string;
5
+ password: string;
6
+ }>;
7
+ export declare function getProxyRoutes(): string[];
8
+ export declare function createDevMiddleware(vite: ViteDevServer): Connect.NextHandleFunction;
package/lib/devServer.js CHANGED
@@ -1,31 +1,14 @@
1
1
  import { Client } from "@olenbetong/appframe-data";
2
- import { watch } from "chokidar";
3
2
  import dotenv from "dotenv";
4
- import express from "express";
5
- import fs from "node:fs/promises";
6
- import path from "node:path";
7
- import open from "open";
8
- import tcpPortUsed from "tcp-port-used";
3
+ import { exists } from "fs-extra";
4
+ import { JSDOM } from "jsdom";
5
+ import { resolve } from "node:path";
9
6
  import { importJson } from "./importJson.js";
10
- import { createAppframeProxy } from "./proxy.js";
11
7
  dotenv.config();
12
8
  let appPkg = await importJson("./package.json", true);
13
9
  let { appframe } = appPkg;
14
- let userSession = null;
15
- async function getUserSession() {
16
- if (userSession) {
17
- return userSession;
18
- }
19
- let { hostname, username, password } = await getLoginInfo();
20
- let client = new Client(hostname);
21
- await client.login(username, password);
22
- let userResult = await client.afFetch("/api/system/usersession", {
23
- timeout: 5000,
24
- });
25
- userSession = await userResult.json();
26
- return userSession;
27
- }
28
- function getLoginInfo() {
10
+ export async function getLoginInfo() {
11
+ let { appframe } = await importJson("./package.json", true);
29
12
  const hostname = appframe.proxy?.hostname ?? appframe.deploy?.hostname ?? "dev.obet.no";
30
13
  const { APPFRAME_LOGIN: username, APPFRAME_PWD: password } = process.env;
31
14
  if (!username || !password) {
@@ -33,173 +16,100 @@ function getLoginInfo() {
33
16
  }
34
17
  return { hostname, username, password };
35
18
  }
36
- export function createProxyMiddleware() {
37
- let { hostname, username, password } = getLoginInfo();
38
- const proxy = createAppframeProxy({ hostname, username, password });
39
- try {
40
- let appPkgUrl = `file://${process.cwd()}/package.json`;
41
- watch(appPkgUrl).on("all", async () => {
42
- appPkg = await importJson("./package.json", true);
43
- appframe = appPkg.appframe;
44
- let { hostname } = getLoginInfo();
45
- proxy.setHostname(hostname);
46
- });
47
- }
48
- catch (error) {
49
- console.log(`Failed to watch package.json: ${error.message}`);
50
- }
19
+ export function getProxyRoutes() {
51
20
  const proxyRoutes = [
52
- "/api/*",
53
- "/file/*",
54
- "/filestore/*",
55
- "/static/dbimages*",
56
- "/static/images*",
57
- "/destroy/*",
58
- "/create/*",
59
- "/update/*",
60
- "/retrieve/*",
61
- "/exec/*",
62
- "/report/*",
63
- "/rowcount/*",
64
- "/lib/*",
21
+ "^/api/.*",
22
+ "^/file/.*",
23
+ "^/filestore/.*",
24
+ "^/static/dbimages/.*",
25
+ "^/static/images/.*",
26
+ "^/static/graphics/.*",
27
+ "^/destroy/.*",
28
+ "^/create/.*",
29
+ "^/update/.*",
30
+ "^/retrieve/.*",
31
+ "^/exec/.*",
32
+ "^/report/.*",
33
+ "^/rowcount/.*",
34
+ "^/lib/.*",
35
+ "^/login",
36
+ "^/logout",
65
37
  ];
66
38
  if (appframe.proxy?.routes) {
67
39
  proxyRoutes.push(...appframe.proxy.routes);
68
40
  }
69
- const router = express.Router();
70
- proxyRoutes.forEach((route) => {
71
- router.use(route, proxy);
72
- });
73
- return router;
41
+ return proxyRoutes;
74
42
  }
75
- export function createHtmlTransformer() {
76
- return async (html) => {
77
- let isPartner = appPkg.appframe?.article?.hostname === "partner.olenbetong.no";
78
- let userSession = await getUserSession();
79
- return html
80
- .replace("<!-- {Appframe} -->", `<script src="/lib/@olenbetong/appframe-core/2.7.3/dist/iife/af.common.js"></script>
81
- <script src="/lib/@olenbetong/appframe-data/0.8.4/dist/iife/af.data.js"></script>
82
- <template id="PromptBeforeReloadDialog">
83
- <dialog class="do-prompt">
84
- <form method="dialog">
85
- <article>
86
- <p>There are unsaved changes. Would you like to save before reloading data?
87
- </article>
88
- <footer>
89
- <menu>
90
- <li><button value="cancel">Cancel reloading</button></li>
91
- <li><button value="reload">Reload without saving</button></li>
92
- <li><button class="primary" value="save-and-reload">Save and reload</button></li>
93
- </menu>
94
- </footer>
95
- </form>
96
- </dialog>
97
- </template>
98
-
99
- <script type="text/javascript">
100
- function waitForDialog(dialog) {
101
- return new Promise((resolve) => {
102
- dialog.addEventListener("close", resolve, { once: true });
43
+ async function getArticleHtml() {
44
+ let { hostname, username, password } = await getLoginInfo();
45
+ let article = appframe.article?.id;
46
+ let client = new Client(hostname);
47
+ await client.login(username, password);
48
+ let response = await client.afFetch(`/${article}`, {
49
+ timeout: 30000,
103
50
  });
104
- }
105
- async function promptBeforeDataObjectReload() {
106
- let template = document.querySelector("#PromptBeforeReloadDialog");
107
- let dialog = template.content.firstElementChild.cloneNode(true);
108
- document.body.appendChild(dialog);
109
- dialog.showModal();
110
-
111
- await waitForDialog(dialog);
112
- let result = dialog.returnValue || "cancel";
113
- document.body.removeChild(dialog);
114
-
115
- if (result === "save-and-reload") {
116
- await this.endEdit();
117
- }
118
-
119
- return result !== "cancel";
120
- }
121
-
122
- af.DataObject.prototype.beforeReload = promptBeforeDataObjectReload;
123
- </script>
124
- <script>
125
- af = globalThis.af ?? {};
126
- af.userSession = ${JSON.stringify(userSession)};
127
- </script>
128
- <script src="/file/article/static-script/${appframe.article.id}.js"></script>
129
- <script>
130
- var l = window.af?.common?.getLocalizedString ?? ((s) => s);
131
- var SkipToConfig = {
132
- 'settings': {
133
- 'skipTo': {
134
- landmarks: 'main, [role="main"], [role="search"], nav, [role="navigation"], aside, [role="complementary"], section',
135
- headings: 'main h1, main h2, main h3',
136
- colorTheme: 'default',
137
- displayOption: "popup",
138
- positionLeft: "0",
139
- buttonLabel: l("Skip to Content"),
140
- buttonTooltipAccesskey: l('Accesskey is "$key"'),
141
- menuLabel: l('Landmarks and Headings'),
142
- landmarkGroupLabel: l('Landmarks'),
143
- headingGroupLabel: l('Headings'),
144
- mofnGroupLabel: l('$m of $n'),
145
- headingLevelLabel: l('Heading level'),
146
- mainLabel: l('main'),
147
- searchLabel: l('search'),
148
- navLabel: l('navigation'),
149
- asideLabel: l('aside'),
150
- footerLabel: l('footer'),
151
- headerLabel: l('header'),
152
- formLabel: l('form'),
153
- msgNoLandmarksFound: l('No landmarks to skip to'),
154
- msgNoHeadingsFound: l('No main headings to skip to')
51
+ if (response.ok) {
52
+ let html = await response.text();
53
+ let entry;
54
+ if (await exists(resolve(process.cwd(), "./src/index.tsx"))) {
55
+ entry = "/src/index.tsx";
56
+ }
57
+ else if (await exists(resolve(process.cwd(), "./src/index.ts"))) {
58
+ entry = "/src/index.ts";
59
+ }
60
+ else if (await exists(resolve(process.cwd(), "./src/index.js"))) {
61
+ entry = "/src/index.js";
62
+ }
63
+ let dom = new JSDOM(html);
64
+ let nodesToRemove = [];
65
+ dom.window.document.querySelectorAll("script").forEach((script) => {
66
+ if (script.src?.startsWith(`/file/article/script/${article}/main`)) {
67
+ let mainScript = dom.window.document.createElement("script");
68
+ mainScript.type = "module";
69
+ mainScript.src = entry;
70
+ script.insertAdjacentElement("beforebegin", mainScript);
71
+ nodesToRemove.push(script);
72
+ }
73
+ else if (script.src?.includes("bugsnag")) {
74
+ nodesToRemove.push(script);
75
+ }
76
+ else if (script.textContent?.includes("bugsnagOptions") ||
77
+ script.textContent?.includes("web-vitals") ||
78
+ script.textContent?.includes("serviceWorker.register")) {
79
+ nodesToRemove.push(script);
80
+ }
81
+ });
82
+ dom.window.document.querySelectorAll("link").forEach((link) => {
83
+ if (link.rel === "stylesheet" && link.href?.startsWith(`/file/article/style/${article}`)) {
84
+ nodesToRemove.push(link);
85
+ }
86
+ else if (link.rel === "prefetch" && link.href?.startsWith(`/file/article`)) {
87
+ nodesToRemove.push(link);
88
+ }
89
+ });
90
+ nodesToRemove.forEach((node) => node.remove());
91
+ return dom.serialize();
155
92
  }
156
- }
157
- };
158
- </script>
159
- <script src="/lib/paypal-skipto/downloads/js/skipto.min.js" async></script>`)
160
- .replace(`<!-- {Theme} -->`, isPartner
161
- ? `
162
- <link rel="stylesheet" href="/file/site/style/ob.application.min.css" />
163
- <link rel="stylesheet" href="/file/site/style/ob.theme.base.less" />
164
- <link rel="stylesheet" href="/file/site/style/ob.theme.betongost.less" />
165
- <link rel="stylesheet" href="/file/site/style/ob.theme.olenbetong.less" />
166
- <link rel="stylesheet" href="/file/site/style/ob.theme.ribe.less" />
167
- <link rel="stylesheet" href="/file/site/style/ob.theme.sjb.less" />
168
- `
169
- : `
170
- <link rel="stylesheet" href="/file/site/style/ob.theme.base.less" />
171
- <link rel="stylesheet" href="/file/site/style/ob.theme.betongost.less" />
172
- <link rel="stylesheet" href="/file/site/style/ob.theme.forsand.less" />
173
- <link rel="stylesheet" href="/file/site/style/ob.theme.olenbetong.less" />
174
- <link rel="stylesheet" href="/file/site/style/ob.theme.ribe.less" />
175
- <link rel="stylesheet" href="/file/site/style/ob.theme.sjb.less" />
176
- <link rel="stylesheet" href="/file/site/style/ob.es.application.min.css" />`);
177
- };
93
+ throw Error(`Failed to get article '${article}' from '${hostname}': ${response.status} ${response.statusText}`);
178
94
  }
179
95
  export function createDevMiddleware(vite) {
180
96
  return async (req, res, next) => {
181
- let transformer = createHtmlTransformer();
182
97
  const url = req.originalUrl;
183
- try {
184
- let template = await fs.readFile(path.resolve(process.cwd(), "index.html"), "utf-8");
185
- template = await vite.transformIndexHtml(url, template);
186
- let html = await transformer(template);
187
- res.status(200).set({ "Content-Type": "text/html" }).end(html);
98
+ if (url === "/" || url?.startsWith("/" + appPkg.appframe.article.id)) {
99
+ try {
100
+ let html = await getArticleHtml();
101
+ html = await vite.transformIndexHtml(url ?? "", html);
102
+ res.statusCode = 200;
103
+ res.setHeader("Content-Type", "text/html");
104
+ res.end(html);
105
+ }
106
+ catch (error) {
107
+ vite.ssrFixStacktrace(error);
108
+ next(error);
109
+ }
188
110
  }
189
- catch (error) {
190
- vite.ssrFixStacktrace(error);
191
- next(error);
111
+ else {
112
+ next();
192
113
  }
193
114
  };
194
115
  }
195
- export async function startDevServer(app) {
196
- let port = 3000;
197
- let used = true;
198
- do {
199
- used = await tcpPortUsed.check(port);
200
- if (used)
201
- port++;
202
- } while (used);
203
- app.listen(port);
204
- open(`http://localhost:${port}/${appframe.article.id}`);
205
- }
package/lib/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Plugin } from "vite";
2
2
  import { addAppframeBuildConfig } from "./build.js";
3
- import { createDevMiddleware, createProxyMiddleware, startDevServer } from "./devServer.js";
3
+ import { createDevMiddleware } from "./devServer.js";
4
4
  export default function appframe(): Plugin;
5
- export { addAppframeBuildConfig };
6
- export { createDevMiddleware, createProxyMiddleware, startDevServer };
5
+ export { addAppframeBuildConfig, createDevMiddleware };
package/lib/index.js CHANGED
@@ -1,15 +1,45 @@
1
+ import { watch } from "chokidar";
1
2
  import { addAppframeBuildConfig } from "./build.js";
2
- import { createDevMiddleware, createHtmlTransformer, createProxyMiddleware, startDevServer } from "./devServer.js";
3
+ import { createDevMiddleware, getLoginInfo, getProxyRoutes } from "./devServer.js";
4
+ import { importJson } from "./importJson.js";
5
+ import { getLastSession, login } from "./proxy.js";
6
+ let interval;
7
+ let server;
8
+ let lastHostname;
9
+ try {
10
+ let appPkgUrl = `file://${process.cwd()}/package.json`;
11
+ watch(appPkgUrl).on("all", async () => {
12
+ if (server) {
13
+ let appPkg = await importJson("./package.json", true);
14
+ let config = appPkg.appframe;
15
+ let { hostname } = await getLoginInfo();
16
+ if (lastHostname !== hostname) {
17
+ server.restart(false);
18
+ }
19
+ }
20
+ });
21
+ }
22
+ catch (error) {
23
+ console.log(`Failed to watch package.json: ${error.message}`);
24
+ }
3
25
  export default function appframe() {
4
- let transformIndexHtml = createHtmlTransformer();
5
26
  return {
6
27
  name: "appframe",
28
+ resolveId(source) {
29
+ if (source.startsWith("/file/") || source.startsWith("/lib/")) {
30
+ return false;
31
+ }
32
+ return null;
33
+ },
7
34
  async config(currentConfig, env) {
8
35
  const { command } = env;
9
36
  let config = {
37
+ ...currentConfig,
10
38
  build: {
39
+ ...currentConfig.build,
11
40
  target: currentConfig.build?.target ?? ["chrome90", "safari14"],
12
41
  rollupOptions: {
42
+ ...currentConfig.build?.rollupOptions,
13
43
  output: {
14
44
  assetFileNames: (assetInfo) => {
15
45
  if (assetInfo.name === "style.css")
@@ -20,23 +50,59 @@ export default function appframe() {
20
50
  },
21
51
  },
22
52
  resolve: {
53
+ ...currentConfig.resolve,
23
54
  alias: [{ find: "~/", replacement: "/src/" }],
24
55
  },
25
56
  };
26
57
  if (command === "build") {
27
58
  await addAppframeBuildConfig(config);
28
59
  }
60
+ let proxy = config.server?.proxy ?? {};
61
+ let routes = getProxyRoutes();
62
+ let { hostname, username, password } = await getLoginInfo();
63
+ lastHostname = hostname;
64
+ await login(hostname, username, password);
65
+ if (interval)
66
+ clearInterval(interval);
67
+ // After changing the plugin to use the Vite proxy instead of a custom
68
+ // express server with node-http-proxy, we can no longer ensure the requests
69
+ // have a valid login. To reduce the amount of problems we get with expired
70
+ // sessions, we run the login procedure every 5 minutes to renew the session.
71
+ interval = setInterval(async () => {
72
+ await login(hostname, username, password);
73
+ }, 1000 * 60 * 5);
74
+ for (let route of routes) {
75
+ proxy[route] = {
76
+ target: `https://${hostname}`,
77
+ changeOrigin: true,
78
+ configure(proxy, options) {
79
+ proxy.on("proxyReq", (proxyReq, req, res, options) => {
80
+ let authCookies = getLastSession(hostname)?.authCookies;
81
+ if (authCookies) {
82
+ let cookies = [];
83
+ cookies.push(`AppframeWebAuth=${authCookies["AppframeWebAuth"]}`);
84
+ cookies.push(`AppframeWebSession=${authCookies["AppframeWebSession"]}`);
85
+ proxyReq.setHeader("cookie", cookies.join(";"));
86
+ // If left unchanged, the origin will be the local IP address, which makes Appframe
87
+ // return 403 errors
88
+ proxyReq.setHeader("origin", `https://${hostname}`);
89
+ }
90
+ });
91
+ },
92
+ };
93
+ }
94
+ config.server = {
95
+ ...config.server,
96
+ proxy,
97
+ };
29
98
  return config;
30
99
  },
31
100
  async configureServer(_server) {
32
- let proxyMiddleware = createProxyMiddleware();
33
- _server.middlewares.use(proxyMiddleware);
101
+ server = _server;
34
102
  return () => {
35
103
  _server.middlewares.use(createDevMiddleware(_server));
36
104
  };
37
105
  },
38
- transformIndexHtml,
39
106
  };
40
107
  }
41
- export { addAppframeBuildConfig };
42
- export { createDevMiddleware, createProxyMiddleware, startDevServer };
108
+ export { addAppframeBuildConfig, createDevMiddleware };
package/lib/proxy.d.ts CHANGED
@@ -1,18 +1,8 @@
1
- import e from "express";
2
1
  type CookieObject = Record<string, string>;
3
- export declare function login(hostname: string, username: string, password: string): Promise<CookieObject>;
4
- export type createProxyOptions = {
5
- /**
6
- * If true, will wait for the first login request to complete before returning.
7
- */
8
- autoLogin?: boolean;
9
- hostname: string;
10
- username: string;
11
- password: string;
12
- protocol?: "http" | "https";
13
- };
14
- export type AppframeProxy = e.RequestHandler & {
15
- setHostname: (hostname: string) => Promise<void>;
2
+ type LoginCacheEntry = {
3
+ timestamp: number;
4
+ authCookies: CookieObject;
16
5
  };
17
- export declare function createAppframeProxy(options: createProxyOptions): AppframeProxy;
6
+ export declare function getLastSession(hostname: any): LoginCacheEntry | undefined;
7
+ export declare function login(hostname: string, username: string, password: string): Promise<CookieObject>;
18
8
  export {};
package/lib/proxy.js CHANGED
@@ -1,7 +1,10 @@
1
- import expressProxy from "express-http-proxy";
1
+ import chalk from "chalk";
2
2
  import https from "node:https";
3
3
  const lastLogin = new Map();
4
4
  let currentLogin = null;
5
+ export function getLastSession(hostname) {
6
+ return lastLogin.get(hostname);
7
+ }
5
8
  export async function login(hostname, username, password) {
6
9
  if (currentLogin) {
7
10
  await currentLogin;
@@ -14,7 +17,7 @@ export async function login(hostname, username, password) {
14
17
  return;
15
18
  }
16
19
  else {
17
- console.log("Renewing authentication cookies...");
20
+ process.stdout.write(`${chalk.bgBlueBright(hostname)}: Renewing authentication cookies...`);
18
21
  lastLogin.delete(hostname);
19
22
  }
20
23
  }
@@ -30,8 +33,14 @@ export async function login(hostname, username, password) {
30
33
  "Content-Length": data.length,
31
34
  },
32
35
  };
33
- console.log("Authenticating...");
36
+ process.stdout.clearLine(0);
37
+ process.stdout.write(`\r${chalk.bgBlueBright(hostname)}: Authenticating`);
38
+ let start = performance.now();
39
+ let interval = setInterval(() => {
40
+ process.stdout.write(`.`);
41
+ }, 100);
34
42
  let request = https.request(options, (response) => {
43
+ clearInterval(interval);
35
44
  let status = response.statusCode ?? 600;
36
45
  if (status < 400) {
37
46
  let cookies = response.headers["set-cookie"] ?? [];
@@ -43,7 +52,8 @@ export async function login(hostname, username, password) {
43
52
  cookieObj[key] = value;
44
53
  }
45
54
  }
46
- console.log("Authentication successfull.");
55
+ process.stdout.clearLine(0);
56
+ process.stdout.write(`\r${chalk.bgBlueBright(hostname)}: Authenticated (${Math.floor(performance.now() - start)}ms)\n`);
47
57
  lastLogin.set(hostname, {
48
58
  timestamp: Date.now(),
49
59
  authCookies: cookieObj,
@@ -51,7 +61,7 @@ export async function login(hostname, username, password) {
51
61
  resolve(cookieObj);
52
62
  }
53
63
  else {
54
- reject(Error(`Authentication failed: ${response.statusCode} ${response.statusMessage}`));
64
+ reject(Error(`${chalk.bgBlueBright(hostname)}: Authentication failed: ${response.statusCode} ${response.statusMessage}`));
55
65
  }
56
66
  });
57
67
  request.write(data);
@@ -71,32 +81,3 @@ export async function login(hostname, username, password) {
71
81
  throw error;
72
82
  }
73
83
  }
74
- export function createAppframeProxy(options) {
75
- let { hostname, password, protocol = "https", username } = options;
76
- // @ts-ignore
77
- const proxy = expressProxy(() => `${protocol}://${hostname}`, {
78
- memoizeHost: false,
79
- proxyReqOptDecorator: async function (proxyReqOpts) {
80
- if (!proxyReqOpts.path?.startsWith("/login")) {
81
- let authCookies = await login(hostname, username, password);
82
- let cookies = [];
83
- cookies.push(`AppframeWebAuth=${authCookies["AppframeWebAuth"]}`);
84
- cookies.push(`AppframeWebSession=${authCookies["AppframeWebSession"]}`);
85
- proxyReqOpts.headers = proxyReqOpts.headers ?? {};
86
- proxyReqOpts.headers["cookie"] = cookies.join(";");
87
- }
88
- return proxyReqOpts;
89
- },
90
- proxyReqPathResolver: function (req) {
91
- return req.originalUrl;
92
- },
93
- });
94
- proxy.setHostname = async (newHostname) => {
95
- if (hostname !== newHostname) {
96
- hostname = newHostname;
97
- console.log(`Changing proxy host to '${newHostname}'...`);
98
- await login(hostname, username, password);
99
- }
100
- };
101
- return proxy;
102
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-vite",
3
- "version": "3.1.17",
3
+ "version": "4.0.0-rc.1",
4
4
  "description": "Tools to use and deploy Vite applications to Appframe",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -20,12 +20,15 @@
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "@olenbetong/appframe-data": "^0.8.10",
23
+ "chalk": "^5.3.0",
23
24
  "chokidar": "^3.5.3",
24
25
  "dotenv": "^16.3.1",
26
+ "jsdom": "^22.1.0",
25
27
  "rollup-plugin-visualizer": "^5.9.2",
26
28
  "vite-plugin-externals": "^0.6.2"
27
29
  },
28
30
  "devDependencies": {
31
+ "@types/jsdom": "^21.1.2",
29
32
  "typescript": "^5.1.6"
30
33
  },
31
34
  "peerDependencies": {
@@ -40,5 +43,5 @@
40
43
  "open": "^9.1.0",
41
44
  "tcp-port-used": "^1.0.2"
42
45
  },
43
- "gitHead": "cfb5910e5d7e2159b65a467ee8ba31b31c74370e"
46
+ "gitHead": "2e06c4e76d3a0e56aec830b06f9dc5491e5291df"
44
47
  }