underpost 2.85.1 → 2.85.7

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/src/cli/run.js CHANGED
@@ -5,7 +5,14 @@
5
5
  */
6
6
 
7
7
  import { daemonProcess, getTerminalPid, openTerminal, pbcopy, shellCd, shellExec } from '../server/process.js';
8
- import { getNpmRootPath, getUnderpostRootPath, isDeployRunnerContext } from '../server/conf.js';
8
+ import {
9
+ awaitDeployMonitor,
10
+ Config,
11
+ getNpmRootPath,
12
+ getUnderpostRootPath,
13
+ isDeployRunnerContext,
14
+ writeEnv,
15
+ } from '../server/conf.js';
9
16
  import { actionInitLog, loggerFactory } from '../server/logger.js';
10
17
  import UnderpostTest from './test.js';
11
18
  import fs from 'fs-extra';
@@ -15,6 +22,7 @@ import UnderpostRootEnv from './env.js';
15
22
  import UnderpostRepository from './repository.js';
16
23
  import os from 'os';
17
24
  import Underpost from '../index.js';
25
+ import dotenv from 'dotenv';
18
26
 
19
27
  const logger = loggerFactory(import.meta);
20
28
 
@@ -44,6 +52,12 @@ class UnderpostRun {
44
52
  * @property {boolean} k3s - Whether to run in k3s mode.
45
53
  * @property {boolean} kubeadm - Whether to run in kubeadm mode.
46
54
  * @property {boolean} force - Whether to force the operation.
55
+ * @property {boolean} reset - Whether to reset the operation.
56
+ * @property {boolean} tls - Whether to use TLS.
57
+ * @property {string} tty - The TTY option for the container.
58
+ * @property {string} stdin - The stdin option for the container.
59
+ * @property {string} restartPolicy - The restart policy for the container.
60
+ * @property {boolean} terminal - Whether to open a terminal.
47
61
  * @memberof UnderpostRun
48
62
  */
49
63
  static DEFAULT_OPTION = {
@@ -59,6 +73,13 @@ class UnderpostRun {
59
73
  k3s: false,
60
74
  kubeadm: false,
61
75
  force: false,
76
+ reset: false,
77
+ tls: false,
78
+ tty: '',
79
+ stdin: '',
80
+ restartPolicy: '',
81
+ terminal: false,
82
+ devProxyPortOffset: 0,
62
83
  };
63
84
  /**
64
85
  * @static
@@ -106,13 +127,21 @@ class UnderpostRun {
106
127
  },
107
128
  /**
108
129
  * @method kill
109
- * @description Kills the process running on the specified port by finding its PID using `lsof -t -i:${path}`.
130
+ * @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
110
131
  * @param {string} path - The input value, identifier, or path for the operation (used as the port number).
111
132
  * @param {Object} options - The default underpost runner options for customizing workflow
112
133
  * @memberof UnderpostRun
113
134
  */
114
- kill: (path, options = UnderpostRun.DEFAULT_OPTION) => {
115
- shellExec(`sudo kill -9 $(lsof -t -i:${path})`);
135
+ kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
136
+ for (const _path of path.split(',')) {
137
+ if (_path.split('+')[1]) {
138
+ let [port, sumPortOffSet] = _path.split('+');
139
+ port = parseInt(port);
140
+ sumPortOffSet = parseInt(sumPortOffSet);
141
+ for (const sumPort of range(0, sumPortOffSet))
142
+ shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
143
+ } else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
144
+ }
116
145
  },
117
146
  /**
118
147
  * @method secret
@@ -330,7 +359,9 @@ class UnderpostRun {
330
359
  if (!fs.existsSync(`/home/dd/engine/engine-private`))
331
360
  shellExec(`cd /home/dd/engine && underpost clone ${process.env.GITHUB_USERNAME}/engine-private`);
332
361
  else
333
- shellExec(`cd /home/dd/engine/engine-private underpost pull . ${process.env.GITHUB_USERNAME}/engine-private`);
362
+ shellExec(
363
+ `cd /home/dd/engine/engine-private && underpost pull . ${process.env.GITHUB_USERNAME}/engine-private`,
364
+ );
334
365
  },
335
366
  /**
336
367
  * @method release-deploy
@@ -440,6 +471,16 @@ class UnderpostRun {
440
471
  'ls-deployments': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
441
472
  console.table(await UnderpostDeploy.API.get(path, 'deployments'));
442
473
  },
474
+ /**
475
+ * @method ls-images
476
+ * @description Retrieves and logs a table of currently loaded Docker images in the 'kind-worker' node using `UnderpostDeploy.API.getCurrentLoadedImages`.
477
+ * @param {string} path - The input value, identifier, or path for the operation.
478
+ * @param {Object} options - The default underpost runner options for customizing workflow
479
+ * @memberof UnderpostRun
480
+ */
481
+ 'ls-images': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
482
+ console.table(UnderpostDeploy.API.getCurrentLoadedImages('kind-worker', false));
483
+ },
443
484
 
444
485
  /**
445
486
  * @method host-update
@@ -608,10 +649,16 @@ class UnderpostRun {
608
649
  * @memberof UnderpostRun
609
650
  */
610
651
  'git-conf': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
611
- const defaultUsername = UnderpostRootEnv.API.get('GITHUB_USERNAME', '', { disableLog: true });
612
- const defaultEmail = UnderpostRootEnv.API.get('GITHUB_EMAIL', '', { disableLog: true });
613
- const [username, email] = path && path.split(',').length > 0 ? path.split(',') : [defaultUsername, defaultEmail];
614
-
652
+ const defaultUsername = UnderpostRootEnv.API.get('GITHUB_USERNAME');
653
+ const defaultEmail = UnderpostRootEnv.API.get('GITHUB_EMAIL');
654
+ const validPath = path && path.split(',').length;
655
+ const [username, email] = validPath ? path.split(',') : [defaultUsername, defaultEmail];
656
+ if (validPath) {
657
+ UnderpostRootEnv.API.set('GITHUB_USERNAME', username);
658
+ UnderpostRootEnv.API.set('GITHUB_EMAIL', email);
659
+ UnderpostRootEnv.API.get('GITHUB_USERNAME');
660
+ UnderpostRootEnv.API.get('GITHUB_EMAIL');
661
+ }
615
662
  shellExec(
616
663
  `git config --global credential.helper "" && ` +
617
664
  `git config credential.helper "" && ` +
@@ -750,6 +797,47 @@ class UnderpostRun {
750
797
  UnderpostDeploy.API.switchTraffic(deployId, env, targetTraffic);
751
798
  },
752
799
 
800
+ /**
801
+ * @method dev
802
+ * @description Starts development servers for client, API, and proxy based on provided parameters (deployId, host, path, clientHostPort).
803
+ * @param {string} path - The input value, identifier, or path for the operation (formatted as `deployId,subConf,host,path,clientHostPort`).
804
+ * @param {Object} options - The default underpost runner options for customizing workflow
805
+ * @memberof UnderpostRun
806
+ */
807
+ dev: async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
808
+ let [deployId, subConf, host, _path, clientHostPort] = path.split(',');
809
+ if (!deployId) deployId = 'dd-default';
810
+ if (!host) host = 'default.net';
811
+ if (!_path) _path = '/';
812
+ if (!clientHostPort) clientHostPort = 'localhost:4004';
813
+ if (!subConf) subConf = 'local';
814
+ if (options.reset && fs.existsSync(`./engine-private/conf/${deployId}`))
815
+ fs.removeSync(`./engine-private/conf/${deployId}`);
816
+ if (!fs.existsSync(`./engine-private/conf/${deployId}`)) Config.deployIdFactory(deployId, { subConf });
817
+ if (options.devProxyPortOffset) {
818
+ const envPath = `./engine-private/conf/${deployId}/.env.development`;
819
+ const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
820
+ envObj.DEV_PROXY_PORT_OFFSET = options.devProxyPortOffset;
821
+ writeEnv(envPath, envObj);
822
+ }
823
+ shellExec(`node bin run dev-cluster expose`);
824
+ {
825
+ const cmd = `npm run dev-api ${deployId} ${subConf} ${host} ${_path} ${clientHostPort}${options.tls ? ' tls' : ''}`;
826
+ options.terminal ? openTerminal(cmd) : shellExec(cmd, { async: true });
827
+ }
828
+ await awaitDeployMonitor(true);
829
+ {
830
+ const cmd = `npm run dev-client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
831
+ options.terminal
832
+ ? openTerminal(cmd)
833
+ : shellExec(cmd, {
834
+ async: true,
835
+ });
836
+ }
837
+ await awaitDeployMonitor(true);
838
+ shellExec(`npm run dev-proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`);
839
+ },
840
+
753
841
  /**
754
842
  * @method service
755
843
  * @description Deploys and exposes specific services (like `mongo-express-service`) on the cluster, updating deployment configurations and monitoring status.
@@ -808,6 +896,21 @@ class UnderpostRun {
808
896
  }
809
897
  },
810
898
 
899
+ /**
900
+ * @method release-cmt
901
+ * @description Commits and pushes a new release for the `engine` repository with a message indicating the new version.
902
+ * @param {string} path - The input value, identifier, or path for the operation.
903
+ * @param {Object} options - The default underpost runner options for customizing workflow
904
+ * @memberof UnderpostRun
905
+ */
906
+ 'release-cmt': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
907
+ shellExec(`underpost run pull`);
908
+ shellExec(`underpost run secret`);
909
+ shellCd(`/home/dd/engine`);
910
+ shellExec(`underpost cmt --empty . ci engine ' New engine release $(underpost --version)'`);
911
+ shellExec(`underpost push . ${process.env.GITHUB_USERNAME}/engine`);
912
+ },
913
+
811
914
  /**
812
915
  * @method sync-replica
813
916
  * @description Syncs a replica for the dd.router
@@ -896,6 +999,9 @@ class UnderpostRun {
896
999
  const volumeMountPath = options.volumeMountPath || path;
897
1000
  const volumeHostPath = options.volumeHostPath || path;
898
1001
  const enableVolumeMount = volumeHostPath && volumeMountPath;
1002
+ const tty = options.tty ? 'true' : 'false';
1003
+ const stdin = options.stdin ? 'true' : 'false';
1004
+ const restartPolicy = options.restartPolicy || 'Never';
899
1005
 
900
1006
  if (options.volumeType === 'dev') options.volumeType = 'FileOrCreate';
901
1007
  const volumeType =
@@ -909,15 +1015,17 @@ kind: Pod
909
1015
  metadata:
910
1016
  name: ${podName}
911
1017
  namespace: ${namespace}
1018
+ labels:
1019
+ app: ${podName}
912
1020
  spec:
913
- restartPolicy: Never
1021
+ restartPolicy: ${restartPolicy}
914
1022
  ${runtimeClassName ? ` runtimeClassName: ${runtimeClassName}` : ''}
915
1023
  containers:
916
1024
  - name: ${containerName}
917
1025
  image: ${imageName}
918
1026
  imagePullPolicy: IfNotPresent
919
- tty: true
920
- stdin: true
1027
+ tty: ${tty}
1028
+ stdin: ${stdin}
921
1029
  command: ${JSON.stringify(options.command ? options.command : ['/bin/bash', '-c'])}
922
1030
  ${
923
1031
  args.length > 0
@@ -953,7 +1061,7 @@ ${
953
1061
  : ''
954
1062
  }
955
1063
  EOF`;
956
- shellExec(`kubectl delete pod ${podName}`);
1064
+ shellExec(`kubectl delete pod ${podName} --ignore-not-found`);
957
1065
  console.log(cmd);
958
1066
  shellExec(cmd, { disableLog: true });
959
1067
  const successInstance = await UnderpostTest.API.statusMonitor(podName);
@@ -101,6 +101,7 @@ const cap = (str) =>
101
101
 
102
102
  const capFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
103
103
 
104
+ // Other option: Array.from(new Set(arr))
104
105
  const uniqueArray = (arr) => arr.filter((item, pos) => arr.indexOf(item) == pos);
105
106
 
106
107
  const orderArrayFromAttrInt = (arr, attr, type) =>
@@ -35,8 +35,12 @@ const SocketIo = {
35
35
  // forceNew: true,
36
36
  // reconnectionAttempts: 'Infinity',
37
37
  // timeout: 10000,
38
- // withCredentials: true,
39
38
  // autoConnect: 5000,
39
+ // Custom auth socket io credentials:
40
+ withCredentials: true,
41
+ extraHeaders: {
42
+ // "my-custom-header": "abcd"
43
+ },
40
44
  transports: ['websocket', 'polling', 'flashsocket'],
41
45
  };
42
46
  // logger.error(`connect options:`, JSON.stringify(connectOptions, null, 4));
@@ -1,108 +1,204 @@
1
- const PRE_CACHED_RESOURCES = self.renderPayload?.PRE_CACHED_RESOURCES ? self.renderPayload.PRE_CACHED_RESOURCES : [];
2
- const CACHE_NAME = self.renderPayload?.CACHE_NAME ? self.renderPayload.CACHE_NAME : 'app-cache';
3
- const PROXY_PATH = self.renderPayload?.PROXY_PATH ? self.renderPayload.PROXY_PATH : '/';
4
- self.addEventListener('install', (event) => {
5
- // Activate right away
6
- self.skipWaiting();
7
-
8
- event.waitUntil(
9
- (async () => {
10
- // Open the app's cache.
11
- const cache = await caches.open(CACHE_NAME);
12
- // Cache all static resources.
13
- try {
14
- await cache.addAll(PRE_CACHED_RESOURCES);
15
- } catch (error) {
16
- console.error(error);
17
- }
18
- // for (const cacheKey of PRE_CACHED_RESOURCES) {
19
- // try {
20
- // await cache.add(cacheKey);
21
- // } catch (error) {
22
- // console.error(error, cacheKey);
23
- // }
24
- // }
25
- })(),
26
- );
27
- });
28
-
29
- self.addEventListener('activate', (event) => {
30
- event.waitUntil(
31
- (async () => {
32
- // Enable navigation preload if it's supported.
33
- // See https://developers.google.com/web/updates/2017/02/navigation-preload
34
- if ('navigationPreload' in self.registration) {
35
- await self.registration.navigationPreload.enable();
36
- }
37
- })(),
38
- );
39
- // Tell the active service worker to take control of the page immediately.
40
- self.clients.claim();
41
- });
42
-
43
- self.addEventListener('fetch', (event) => {
44
- // Cache-First Strategy
45
- event.respondWith(
46
- (async () => {
47
- // First, try to use the navigation preload response if it's supported.
48
- try {
49
- const preloadResponse = await event.preloadResponse;
50
- if (preloadResponse) return preloadResponse;
51
- return await fetch(event.request);
52
- } catch (error) {
53
- console.error('Fetch failed; returning offline page instead.', event.request.url, error);
54
- // Fallback to the offline page.
55
- const path = PRE_CACHED_RESOURCES.find((path) => event.request.url.match(path.replaceAll('/index.html', '')));
1
+ /**
2
+ * This module provides a configurable Progressive Web App (PWA) service worker and caching strategies.
3
+ * It supports precaching assets, runtime caching with stale-while-revalidate strategy,
4
+ * and offline fallback handling.
5
+ * @module src/client/sw/default.sw.js
6
+ * @namespace PwaServiceWorker
7
+ */
8
+
9
+ /**
10
+ * Class representing a Progressive Web App (PWA) Service Worker with caching strategies.
11
+ * @class
12
+ * @memberof PwaServiceWorker
13
+ */
14
+ class PwaServiceWorker {
15
+ /**
16
+ * Initializes the service worker configuration by reading from self.renderPayload.
17
+ * If properties are not found, defaults are used.
18
+ * @constructor
19
+ * @property {Array<string>} PRE_CACHED_RESOURCES - List of resources to precache.
20
+ * @property {string} CACHE_NAME - Name of the cache storage.
21
+ * @property {string} PROXY_PATH - Base path for proxying requests.
22
+ */
23
+ constructor() {
24
+ // Configuration properties equivalent to the original global constants
25
+ this.PRE_CACHED_RESOURCES = self.renderPayload?.PRE_CACHED_RESOURCES ?? [];
26
+ this.CACHE_NAME = self.renderPayload?.CACHE_NAME ?? 'app-cache';
27
+ this.PROXY_PATH = self.renderPayload?.PROXY_PATH ?? '/';
28
+
29
+ console.log(`Service Worker Initialized. Cache: ${this.CACHE_NAME}, Proxy: ${this.PROXY_PATH}`);
30
+ }
56
31
 
32
+ /**
33
+ * Registers event listeners for the service worker lifecycle and requests.
34
+ * @method
35
+ * @memberof PwaServiceWorker
36
+ */
37
+ run() {
38
+ // Bind methods to 'this' (the instance) before attaching to self
39
+ self.addEventListener('install', this._onInstall.bind(this));
40
+ self.addEventListener('activate', this._onActivate.bind(this));
41
+ self.addEventListener('fetch', this._onFetch.bind(this));
42
+ }
43
+
44
+ /**
45
+ * Handles the 'install' event. Skips waiting and precaches static assets.
46
+ * @param {ExtendableEvent} event
47
+ * @memberof PwaServiceWorker
48
+ */
49
+ _onInstall(event) {
50
+ // Activate right away
51
+ self.skipWaiting();
52
+
53
+ event.waitUntil(
54
+ (async () => {
55
+ // Open the app's cache using the configured name.
56
+ const cache = await caches.open(this.CACHE_NAME);
57
+ // Cache all static resources.
57
58
  try {
58
- const cachedResponse = await caches.match(event.request);
59
- if (cachedResponse) return cachedResponse;
60
- const cache = await caches.open(CACHE_NAME);
61
- const preCachedResponse = await cache.match(path);
62
- if (!preCachedResponse) throw new Error(error.message);
63
- return preCachedResponse;
59
+ console.log(`Precaching ${this.PRE_CACHED_RESOURCES.length} resources...`);
60
+ await cache.addAll(this.PRE_CACHED_RESOURCES);
64
61
  } catch (error) {
65
- console.error('Error opening cache for pre cached page', {
66
- url: event.request.url,
67
- error,
68
- onLine: navigator.onLine,
69
- });
70
- try {
71
- if (!navigator.onLine) {
72
- if (event.request.method.toUpperCase() === 'GET') {
73
- const cache = await caches.open(CACHE_NAME);
74
- const preCachedResponse = await cache.match(
75
- `${PROXY_PATH === '/' ? '' : PROXY_PATH}/offline/index.html`,
76
- );
77
- if (!preCachedResponse) throw new Error(error.message);
78
- return preCachedResponse;
79
- }
80
- const response = new Response(JSON.stringify({ status: 'error', message: 'offline test response' }));
81
- // response.status = 200;
82
- response.headers.set('Content-Type', 'application/json');
83
- return response;
84
- }
62
+ console.error('Error during precaching resources:', error);
63
+ }
64
+ })(),
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Handles the 'activate' event. Enables navigation preload and takes control
70
+ * of uncontrolled clients immediately.
71
+ * @param {ExtendableEvent} event
72
+ * @memberof PwaServiceWorker
73
+ */
74
+ _onActivate(event) {
75
+ event.waitUntil(
76
+ (async () => {
77
+ // Enable navigation preload if it's supported.
78
+ if ('navigationPreload' in self.registration) {
79
+ await self.registration.navigationPreload.enable();
80
+ console.log('Navigation Preload enabled.');
81
+ }
82
+ })(),
83
+ );
84
+ // Tell the active service worker to take control of the page immediately.
85
+ self.clients.claim();
86
+ }
87
+
88
+ /**
89
+ * Handles the 'fetch' event, implementing the Cache-First strategy with
90
+ * complex offline and maintenance fallbacks.
91
+ * @param {FetchEvent} event
92
+ * @memberof PwaServiceWorker
93
+ */
94
+ _onFetch(event) {
95
+ // Only handle HTTP/HTTPS requests that are not cross-origin (optional, but robust)
96
+ if (event.request.url.startsWith('http')) {
97
+ event.respondWith(this._handleFetchRequest(event));
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Core logic to handle fetching, caching, and fallbacks.
103
+ * @param {FetchEvent} event
104
+ * @returns {Promise<Response>}
105
+ * @memberof PwaServiceWorker
106
+ */
107
+ async _handleFetchRequest(event) {
108
+ // 1. Try Navigation Preload (if available) or network first
109
+ try {
110
+ const preloadResponse = await event.preloadResponse;
111
+ if (preloadResponse) return preloadResponse;
112
+
113
+ // Fall through to network request if no preload response
114
+ const networkResponse = await fetch(event.request);
115
+
116
+ // OPTIONAL: If the network request is successful, cache it for future use (stale-while-revalidate logic)
117
+ // Omitted for strict equivalence, as original only had complex fallback, not runtime caching.
118
+
119
+ return networkResponse;
120
+ } catch (error) {
121
+ console.error('Network request failed. Attempting cache/fallback logic.', event.request.url, error);
122
+
123
+ // 2. Try to match the request in the cache
124
+ try {
125
+ const cachedResponse = await caches.match(event.request);
126
+ if (cachedResponse) {
127
+ console.log(`Cache hit for: ${event.request.url}`);
128
+ return cachedResponse;
129
+ }
130
+
131
+ // 3. Try to match a precached resource path (e.g., if requesting /page, match /page/index.html)
132
+ const path = this.PRE_CACHED_RESOURCES.find((p) => event.request.url.match(p.replaceAll('/index.html', '')));
133
+
134
+ if (path) {
135
+ const cache = await caches.open(this.CACHE_NAME);
136
+ const preCachedResponse = await cache.match(path);
137
+ if (preCachedResponse) {
138
+ console.log(`Matched precached resource for: ${event.request.url} via path: ${path}`);
139
+ return preCachedResponse;
140
+ }
141
+ }
142
+
143
+ // If neither cache match nor precache path match worked, fall through to complex fallback
144
+ throw new Error('Cache miss and no precache match.');
145
+ } catch (cacheError) {
146
+ console.error('Error in primary cache lookup. Falling back to offline/maintenance pages.', {
147
+ url: event.request.url,
148
+ cacheError,
149
+ onLine: navigator.onLine,
150
+ });
151
+
152
+ // 4. Complex Fallback Logic (Offline or Maintenance)
153
+ try {
154
+ const cache = await caches.open(this.CACHE_NAME);
155
+
156
+ if (!navigator.onLine) {
157
+ // A. OFFLINE FALLBACK
85
158
  if (event.request.method.toUpperCase() === 'GET') {
86
- const cache = await caches.open(CACHE_NAME);
87
- const preCachedResponse = await cache.match(
88
- `${PROXY_PATH === '/' ? '' : PROXY_PATH}/maintenance/index.html`,
89
- );
90
- if (!preCachedResponse) throw new Error(error.message);
159
+ const offlinePath = `${this.PROXY_PATH === '/' ? '' : this.PROXY_PATH}/offline/index.html`;
160
+ const preCachedResponse = await cache.match(offlinePath);
161
+
162
+ if (!preCachedResponse) throw new Error(`Offline page not found in cache: ${offlinePath}`);
163
+
164
+ console.log('Serving offline HTML page.');
91
165
  return preCachedResponse;
92
166
  }
93
- const response = new Response(JSON.stringify({ status: 'error', message: 'server in maintenance' }));
94
- // response.status = 200;
95
- response.headers.set('Content-Type', 'application/json');
96
- return response;
97
- } catch (error) {
98
- console.error('Error opening cache for offline page', event.request.url, error);
99
- const response = new Response(JSON.stringify({ status: 'error', message: error.message }));
100
- // response.status = 200;
167
+
168
+ // B. OFFLINE API FALLBACK (Non-GET requests)
169
+ console.log('Serving offline JSON response for non-GET request.');
170
+ const response = new Response(JSON.stringify({ status: 'error', message: 'offline test response' }));
101
171
  response.headers.set('Content-Type', 'application/json');
102
172
  return response;
103
173
  }
174
+
175
+ // C. MAINTENANCE FALLBACK (Online, but network failed - interpreted as maintenance)
176
+ if (event.request.method.toUpperCase() === 'GET') {
177
+ const maintenancePath = `${this.PROXY_PATH === '/' ? '' : this.PROXY_PATH}/maintenance/index.html`;
178
+ const preCachedResponse = await cache.match(maintenancePath);
179
+
180
+ if (!preCachedResponse) throw new Error(`Maintenance page not found in cache: ${maintenancePath}`);
181
+
182
+ console.log('Serving maintenance HTML page.');
183
+ return preCachedResponse;
184
+ }
185
+
186
+ // D. MAINTENANCE API FALLBACK (Non-GET requests)
187
+ console.log('Serving maintenance JSON response for non-GET request.');
188
+ const response = new Response(JSON.stringify({ status: 'error', message: 'server in maintenance' }));
189
+ response.headers.set('Content-Type', 'application/json');
190
+ return response;
191
+ } catch (finalError) {
192
+ // 5. Final fail-safe response
193
+ console.error('Final fail-safe execution failed.', event.request.url, finalError);
194
+ const response = new Response(JSON.stringify({ status: 'error', message: finalError.message }));
195
+ response.headers.set('Content-Type', 'application/json');
196
+ return response;
104
197
  }
105
198
  }
106
- })(),
107
- );
108
- });
199
+ }
200
+ }
201
+ }
202
+
203
+ // Instantiate and run the service worker class
204
+ new PwaServiceWorker().run();
package/src/client.dev.js CHANGED
@@ -15,7 +15,7 @@ const logger = loggerFactory(import.meta);
15
15
 
16
16
  await logger.setUpInfo();
17
17
 
18
- await buildClientStaticConf();
18
+ await buildClientStaticConf({ devProxy: process.argv[6] === 'proxy' });
19
19
 
20
20
  await Config.build();
21
21
 
package/src/index.js CHANGED
@@ -35,7 +35,7 @@ class Underpost {
35
35
  * @type {String}
36
36
  * @memberof Underpost
37
37
  */
38
- static version = 'v2.85.1';
38
+ static version = 'v2.85.7';
39
39
  /**
40
40
  * Repository cli API
41
41
  * @static
package/src/proxy.js CHANGED
@@ -11,7 +11,7 @@ import { Config } from './server/conf.js';
11
11
 
12
12
  dotenv.config();
13
13
 
14
- await Config.build();
14
+ await Config.build(process.argv[2], process.argv[3], process.argv[4]);
15
15
 
16
16
  const logger = loggerFactory(import.meta);
17
17
 
@@ -23,6 +23,7 @@ import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
23
23
  import { ssrMiddlewareFactory } from '../../server/ssr.js';
24
24
  import { TLS } from '../../server/tls.js';
25
25
  import { shellExec } from '../../server/process.js';
26
+ import { devProxyHostFactory, isDevProxyContext, isTlsDevProxy } from '../../server/conf.js';
26
27
 
27
28
  const logger = loggerFactory(import.meta);
28
29
 
@@ -94,7 +95,9 @@ class ExpressService {
94
95
 
95
96
  const app = express();
96
97
 
97
- if (process.env.NODE_ENV === 'production') app.set('trust proxy', true);
98
+ if (origins && isDevProxyContext())
99
+ origins.push(devProxyHostFactory({ host, includeHttp: true, tls: isTlsDevProxy() }));
100
+ app.set('trust proxy', true);
98
101
 
99
102
  app.use((req, res, next) => {
100
103
  res.on('finish', () => {
@@ -17,6 +17,7 @@ import slowDown from 'express-slow-down';
17
17
  import cors from 'cors';
18
18
  import cookieParser from 'cookie-parser';
19
19
  import { DataBaseProvider } from '../db/DataBaseProvider.js';
20
+ import { isDevProxyContext } from './conf.js';
20
21
 
21
22
  dotenv.config();
22
23
  const logger = loggerFactory(import.meta);
@@ -348,7 +349,7 @@ const cookieOptionsFactory = (req, host) => {
348
349
  secure,
349
350
  sameSite,
350
351
  path: '/',
351
- domain: process.env.NODE_ENV === 'production' ? host : 'localhost',
352
+ domain: process.env.NODE_ENV === 'production' || isDevProxyContext() ? host : 'localhost',
352
353
  maxAge,
353
354
  };
354
355