@underpostnet/underpost 3.0.0 → 3.0.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/.env.development CHANGED
@@ -40,5 +40,4 @@ DEFAULT_ADMIN_EMAIL=admin@default.net
40
40
  DEFAULT_ADMIN_PASSWORD=changethis
41
41
  DEFAULT_SSH_PORT=22
42
42
  BASE_API=api
43
- DEV_PROXY_PORT_OFFSET=200
44
- ENABLE_FILE_LOGS=true
43
+ DEV_PROXY_PORT_OFFSET=200
package/.env.test CHANGED
@@ -40,5 +40,4 @@ DEFAULT_ADMIN_EMAIL=admin@default.net
40
40
  DEFAULT_ADMIN_PASSWORD=changethis
41
41
  DEFAULT_SSH_PORT=22
42
42
  BASE_API=api
43
- DEV_PROXY_PORT_OFFSET=200
44
- ENABLE_FILE_LOGS=true
43
+ DEV_PROXY_PORT_OFFSET=200
@@ -0,0 +1,20 @@
1
+ name: CI | Publish gitlab repository package
2
+ on: [push]
3
+ jobs:
4
+ pwa-microservices-template:
5
+ if: github.repository == 'underpostnet/pwa-microservices-template' && startsWith(github.event.head_commit.message, 'ci(package-pwa-microservices-template-ghpkg)')
6
+ name: Update gitlab repo package Jobs
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ with:
11
+ fetch-depth: 0
12
+ lfs: true
13
+ - name: Push to GitLab
14
+ run: |
15
+ git remote add gitlab https://oauth2:${{ secrets.GITLAB_TOKEN }}@gitlab.com/underpostnet/pwa-microservices-template.git
16
+ git lfs install
17
+ git lfs fetch --all
18
+ git lfs push --all gitlab
19
+ git push gitlab HEAD:main --force
20
+ git push gitlab --force --tags
package/CHANGELOG.md CHANGED
@@ -1,6 +1,55 @@
1
1
  # Changelog
2
2
 
3
- ## 2026-02-22
3
+ ## 2026-02-23
4
+
5
+ ### gitlab
6
+
7
+ - Fix package json lock template build ([e674ec6b](https://github.com/underpostnet/engine/commit/e674ec6be61d7a170ab468d473d0e545401b765a))
8
+ - Fix mirror push to GitLab ([9585aa50](https://github.com/underpostnet/engine/commit/9585aa50ee481fa49084c0edd44cc28b4b2561e8))
9
+
10
+ ### bin-file
11
+
12
+ - Add missing gitlab.ci.yml build to pwa-microservices-template ([ec49ded0](https://github.com/underpostnet/engine/commit/ec49ded0ac3fbfcba1e7e10b0ed1dcfc13a8da87))
13
+
14
+ ### client-core
15
+
16
+ - Add missing keyboard focus search box on iframes docs ([c5b0f86c](https://github.com/underpostnet/engine/commit/c5b0f86c7acc0d2c964cc1ef80625693241e6d62))
17
+ - Add VanillaJs get selector in iframe ([e37fa340](https://github.com/underpostnet/engine/commit/e37fa34037cff9924bc747f1ee11190ee2e1164b))
18
+
19
+ ### giblab
20
+
21
+ - Add .gitlab-ci.yml ([a795bd5f](https://github.com/underpostnet/engine/commit/a795bd5f3526257c858ec70ee27feb8bfd793baf))
22
+
23
+ ### docs
24
+
25
+ - Add VanillaJs get selector in iframe sync darkTheme in docs component. ([5b2ba08f](https://github.com/underpostnet/engine/commit/5b2ba08f3b0df3a6072aa49ca55efd223f72a95c))
26
+
27
+ ### server-client-build-docs
28
+
29
+ - Enable Swagger UI Dark Light Mode ([eaadad70](https://github.com/underpostnet/engine/commit/eaadad70cd74bcd9f7990dd63834bbd69bffcbae))
30
+
31
+ ### github-actions
32
+
33
+ - Add gitlab mirror CI repository integration ([3d6acdef](https://github.com/underpostnet/engine/commit/3d6acdefeea72f26a733975e822dbcf2b4e793e3))
34
+ - Fix GitHub Actions npm provenance ([cd31b8f0](https://github.com/underpostnet/engine/commit/cd31b8f0ed202ed376016d3fc4b9fc63152f5186))
35
+
36
+ ### cli-run
37
+
38
+ - Fix missing cluster type on runners id cluster and gpu env ([ddd72d2e](https://github.com/underpostnet/engine/commit/ddd72d2e32e448b8956862f0719d5ab2d2ea7606))
39
+
40
+ ### package
41
+
42
+ - Resolve npm ci lock mismatch ([357b4e81](https://github.com/underpostnet/engine/commit/357b4e81611541a0d979bc95cb587343bf540604))
43
+
44
+ ### cli-repo
45
+
46
+ - Fix Changelog error due to type integration message ([750656e1](https://github.com/underpostnet/engine/commit/750656e1cbee5dbb3e73d9d5cdd4d94ed049a4f1))
47
+
48
+ ### cli-ipfs
49
+
50
+ - Fix underpost ipfs syntax import in main src index ([f7bebb65](https://github.com/underpostnet/engine/commit/f7bebb6555a85df35aed3e248dd0b304c00fd008))
51
+
52
+ ## New release v:3.0.0 (2026-02-22)
4
53
 
5
54
  ### engine-core
6
55
 
package/CLI-HELP.md CHANGED
@@ -1,4 +1,4 @@
1
- ## underpost ci/cd cli v3.0.0
1
+ ## underpost ci/cd cli v3.0.1
2
2
 
3
3
  ### Usage: `underpost [options] [command]`
4
4
  ```
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  <div align="center">
18
18
 
19
- [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/3.0.0)](https://socket.dev/npm/package/underpost/overview/3.0.0) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
19
+ [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/3.0.1)](https://socket.dev/npm/package/underpost/overview/3.0.1) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
20
20
 
21
21
  </div>
22
22
 
@@ -60,7 +60,7 @@ npm run dev
60
60
 
61
61
  <a target="_top" href="https://www.nexodev.org/docs?cid=src">See Docs here.</a>
62
62
 
63
- ## underpost ci/cd cli v3.0.0
63
+ ## underpost ci/cd cli v3.0.1
64
64
 
65
65
  ### Usage: `underpost [options] [command]`
66
66
  ```
package/bin/file.js CHANGED
@@ -86,7 +86,7 @@ try {
86
86
  // fs.copySync(`./.github`, `../pwa-microservices-template/.github`);
87
87
  fs.copySync(`./src/client/public/default`, `../pwa-microservices-template/src/client/public/default`);
88
88
 
89
- for (const checkoutPath of ['README.md', 'package-lock.json', 'package.json'])
89
+ for (const checkoutPath of ['README.md', 'package.json'])
90
90
  shellExec(`cd ../pwa-microservices-template && git checkout ${checkoutPath}`);
91
91
 
92
92
  for (const deletePath of [
@@ -110,6 +110,7 @@ try {
110
110
  `./.github/workflows/pwa-microservices-template-test.ci.yml`,
111
111
  `./.github/workflows/npmpkg.ci.yml`,
112
112
  `./.github/workflows/ghpkg.ci.yml`,
113
+ `./.github/workflows/gitlab.ci.yml`,
113
114
  `./.github/workflows/publish.ci.yml`,
114
115
  `./.github/workflows/release.cd.yml`,
115
116
  './src/ws/IoInterface.js',
@@ -129,6 +130,7 @@ try {
129
130
  templatePackageJson.devDependencies = originPackageJson.devDependencies;
130
131
  templatePackageJson.version = originPackageJson.version;
131
132
  templatePackageJson.scripts = originPackageJson.scripts;
133
+ templatePackageJson.overrides = originPackageJson.overrides;
132
134
  templatePackageJson.name = name;
133
135
  templatePackageJson.description = description;
134
136
  // templatePackageJson.scripts.dev = dev;
@@ -142,15 +144,17 @@ try {
142
144
  JSON.stringify(templatePackageJson, null, 4),
143
145
  'utf8',
144
146
  );
145
-
146
147
  const originPackageLockJson = JSON.parse(fs.readFileSync('./package-lock.json', 'utf8'));
148
+
147
149
  const templatePackageLockJson = JSON.parse(
148
150
  fs.readFileSync('../pwa-microservices-template/package-lock.json', 'utf8'),
149
151
  );
152
+
150
153
  const originBasePackageLock = newInstance(templatePackageLockJson.packages['']);
154
+ templatePackageLockJson.name = name;
151
155
  templatePackageLockJson.version = originPackageLockJson.version;
152
156
  templatePackageLockJson.packages = originPackageLockJson.packages;
153
- templatePackageLockJson.packages[''].name = originBasePackageLock.name;
157
+ templatePackageLockJson.packages[''].name = name;
154
158
  templatePackageLockJson.packages[''].version = originPackageLockJson.version;
155
159
  templatePackageLockJson.packages[''].hasInstallScript = originBasePackageLock.hasInstallScript;
156
160
  templatePackageLockJson.packages[''].license = originBasePackageLock.license;
@@ -159,6 +163,8 @@ try {
159
163
  JSON.stringify(templatePackageLockJson, null, 4),
160
164
  'utf8',
161
165
  );
166
+ // Regenerate package-lock.json to match the modified package.json
167
+ // shellExec(`cd ../pwa-microservices-template && npm install --package-lock-only --ignore-scripts`);
162
168
  fs.writeFileSync(
163
169
  '../pwa-microservices-template/README.md',
164
170
  fs
@@ -23,7 +23,7 @@ spec:
23
23
  spec:
24
24
  containers:
25
25
  - name: dd-cron-backup
26
- image: underpost/underpost-engine:v3.0.0
26
+ image: underpost/underpost-engine:v3.0.1
27
27
  command:
28
28
  - /bin/sh
29
29
  - -c
@@ -23,7 +23,7 @@ spec:
23
23
  spec:
24
24
  containers:
25
25
  - name: dd-cron-dns
26
- image: underpost/underpost-engine:v3.0.0
26
+ image: underpost/underpost-engine:v3.0.1
27
27
  command:
28
28
  - /bin/sh
29
29
  - -c
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-default-development-blue
20
- image: localhost/rockylinux9-underpost:v3.0.0
20
+ image: localhost/rockylinux9-underpost:v3.0.1
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "124Ki"
@@ -100,7 +100,7 @@ spec:
100
100
  spec:
101
101
  containers:
102
102
  - name: dd-default-development-green
103
- image: localhost/rockylinux9-underpost:v3.0.0
103
+ image: localhost/rockylinux9-underpost:v3.0.1
104
104
  # resources:
105
105
  # requests:
106
106
  # memory: "124Ki"
@@ -18,7 +18,7 @@ spec:
18
18
  spec:
19
19
  containers:
20
20
  - name: dd-test-development-blue
21
- image: localhost/rockylinux9-underpost:v3.0.0
21
+ image: localhost/rockylinux9-underpost:v3.0.1
22
22
 
23
23
  command:
24
24
  - /bin/sh
@@ -103,7 +103,7 @@ spec:
103
103
  spec:
104
104
  containers:
105
105
  - name: dd-test-development-green
106
- image: localhost/rockylinux9-underpost:v3.0.0
106
+ image: localhost/rockylinux9-underpost:v3.0.1
107
107
 
108
108
  command:
109
109
  - /bin/sh
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "@underpostnet/underpost",
5
- "version": "3.0.0",
5
+ "version": "3.0.1",
6
6
  "description": "pwa api rest template",
7
7
  "scripts": {
8
8
  "start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
@@ -108,5 +108,9 @@
108
108
  "provenance": true,
109
109
  "access": "public",
110
110
  "registry": "https://registry.npmjs.org/"
111
+ },
112
+ "overrides": {
113
+ "minimatch": "^10.2.2",
114
+ "glob": "^11.0.0"
111
115
  }
112
116
  }
@@ -99,22 +99,6 @@ const UserRouter = (options) => {
99
99
  #swagger.description = 'This endpoint get a JWT for authenticated user'
100
100
  #swagger.path = '/user/auth'
101
101
  #swagger.method = 'post'
102
- #swagger.produces = ['application/json']
103
- #swagger.consumes = ['application/json']
104
-
105
- #swagger.requestBody = {
106
- in: 'body',
107
- description: 'User data',
108
- required: true,
109
- content: {
110
- 'application/json': {
111
- schema: {
112
- $ref: '#/components/schemas/userLogInRequest'
113
- }
114
- }
115
- }
116
- }
117
-
118
102
  #swagger.responses[200] = {
119
103
  description: 'User created successfully',
120
104
  content: {
@@ -148,22 +132,6 @@ const UserRouter = (options) => {
148
132
  #swagger.description = 'This endpoint will create a new user account'
149
133
  #swagger.path = '/user'
150
134
  #swagger.method = 'post'
151
- #swagger.produces = ['application/json']
152
- #swagger.consumes = ['application/json']
153
-
154
- #swagger.requestBody = {
155
- in: 'body',
156
- description: 'User data',
157
- required: true,
158
- content: {
159
- 'application/json': {
160
- schema: {
161
- $ref: '#/components/schemas/userRequest'
162
- }
163
- }
164
- }
165
- }
166
-
167
135
  #swagger.responses[200] = {
168
136
  description: 'User created successfully',
169
137
  content: {
@@ -274,8 +242,6 @@ const UserRouter = (options) => {
274
242
  #swagger.description = 'This endpoint will update user data by ID'
275
243
  #swagger.path = '/user/{id}'
276
244
  #swagger.method = 'put'
277
- #swagger.produces = ['application/json']
278
- #swagger.consumes = ['application/json']
279
245
  #swagger.security = [{
280
246
  'bearerAuth': []
281
247
  }]
@@ -287,19 +253,6 @@ const UserRouter = (options) => {
287
253
  type: 'string'
288
254
  }
289
255
 
290
- #swagger.requestBody = {
291
- in: 'body',
292
- description: 'User data',
293
- required: true,
294
- content: {
295
- 'application/json': {
296
- schema: {
297
- $ref: '#/components/schemas/userRequest'
298
- }
299
- }
300
- }
301
- }
302
-
303
256
  #swagger.responses[200] = {
304
257
  description: 'User updated successfully',
305
258
  content: {
package/src/cli/lxd.js CHANGED
@@ -344,7 +344,7 @@ ipv6.address=none`);
344
344
  }
345
345
  case 'dev-reset': {
346
346
  shellExec(
347
- `lxc exec ${vmName} -- bash -lc 'cd /home/dd/engine && node bin cluster --dev --reset && node bin cluster --dev --k3s'`,
347
+ `lxc exec ${vmName} -- bash -lc 'cd /home/dd/engine && node bin cluster --dev --reset --k3s && node bin cluster --dev --k3s'`,
348
348
  );
349
349
  break;
350
350
  }
package/src/cli/run.js CHANGED
@@ -1148,7 +1148,7 @@ EOF
1148
1148
  const baseClusterCommand = options.dev ? ' --dev' : '';
1149
1149
  const clusterType = options.k3s ? 'k3s' : 'kubeadm';
1150
1150
  shellCd(`/home/dd/engine`);
1151
- shellExec(`${baseCommand} cluster${baseClusterCommand} --reset`);
1151
+ shellExec(`${baseCommand} cluster${baseClusterCommand} --reset --${clusterType}`);
1152
1152
  await timer(5000);
1153
1153
  shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType}`);
1154
1154
  await timer(5000);
@@ -1779,8 +1779,9 @@ EOF
1779
1779
  * @memberof UnderpostRun
1780
1780
  */
1781
1781
  'gpu-env': (path, options = DEFAULT_OPTION) => {
1782
+ const clusterType = 'kubeadm';
1782
1783
  shellExec(
1783
- `node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu --kubeadm && kubectl get pods --all-namespaces -o wide -w`,
1784
+ `node bin cluster --dev --reset --${clusterType} && node bin cluster --dev --dedicated-gpu --${clusterType} && kubectl get pods --all-namespaces -o wide -w`,
1784
1785
  );
1785
1786
  },
1786
1787
  /**
@@ -1,9 +1,9 @@
1
1
  import { Badge } from './Badge.js';
2
2
  import { BtnIcon } from './BtnIcon.js';
3
- import { Css, renderCssAttr, simpleIconsRender, ThemeEvents, Themes } from './Css.js';
3
+ import { Css, darkTheme, renderCssAttr, simpleIconsRender, ThemeEvents, Themes } from './Css.js';
4
4
  import { buildBadgeToolTipMenuOption, Modal, renderViewTitle } from './Modal.js';
5
5
  import { listenQueryPathInstance, setQueryPath, closeModalRouteChangeEvent, getProxyPath } from './Router.js';
6
- import { htmls, s } from './VanillaJs.js';
6
+ import { htmls, s, sIframe } from './VanillaJs.js';
7
7
 
8
8
  // https://mintlify.com/docs/quickstart
9
9
 
@@ -39,6 +39,7 @@ const Docs = {
39
39
  RouterInstance: Modal.Data['modal-docs'].options.RouterInstance,
40
40
  });
41
41
  const iframeEl = s(`.iframe-${ModalId}`);
42
+ let swaggerThemeEventKey = null;
42
43
  if (iframeEl) {
43
44
  iframeEl.addEventListener('load', () => {
44
45
  try {
@@ -51,7 +52,95 @@ const Docs = {
51
52
  // cross-origin or security restriction — safe to ignore
52
53
  }
53
54
  window.scrollTo(0, 0);
55
+ // Bind Shift+K inside the iframe to focus the parent SearchBox (mirrors app-wide shortcut)
56
+ try {
57
+ const iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow?.document;
58
+ if (iframeDoc) {
59
+ iframeDoc.addEventListener('keydown', (e) => {
60
+ if (e.shiftKey && e.key.toLowerCase() === 'k') {
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ if (s(`.top-bar-search-box`)) {
64
+ if (s(`.main-body-btn-ui-close`) && s(`.main-body-btn-ui-close`).classList.contains('hide')) {
65
+ s(`.main-body-btn-ui-open`).click();
66
+ }
67
+ s(`.top-bar-search-box`).blur();
68
+ s(`.top-bar-search-box`).focus();
69
+ s(`.top-bar-search-box`).select();
70
+ }
71
+ }
72
+ });
73
+ }
74
+ } catch (e) {
75
+ // cross-origin or security restriction — safe to ignore
76
+ }
54
77
  });
78
+
79
+ if (type === 'src') {
80
+ swaggerThemeEventKey = `jsdocs-iframe-${ModalId}`;
81
+
82
+ const applyJsDocsTheme = (isDark) => {
83
+ try {
84
+ const iframeWin = iframeEl.contentWindow;
85
+ if (!iframeWin) return;
86
+ const theme = isDark ? 'dark' : 'light';
87
+ if (typeof iframeWin.updateTheme === 'function') {
88
+ // clean-jsdoc-theme exposes updateTheme() globally
89
+ iframeWin.updateTheme(theme);
90
+ } else {
91
+ // Fallback: replicate localUpdateTheme manually
92
+ const iframeDoc = iframeEl.contentDocument || iframeWin.document;
93
+ if (!iframeDoc || !iframeDoc.body) return;
94
+ iframeDoc.body.setAttribute('data-theme', theme);
95
+ iframeDoc.body.classList.remove('dark', 'light');
96
+ iframeDoc.body.classList.add(theme);
97
+ const iconID = isDark ? '#light-theme-icon' : '#dark-theme-icon';
98
+ const svgUses = sIframe(iframeEl, '.theme-svg-use') ? iframeDoc.querySelectorAll('.theme-svg-use') : [];
99
+ svgUses.forEach((svg) => svg.setAttribute('xlink:href', iconID));
100
+ iframeWin.localStorage?.setItem('theme', theme);
101
+ }
102
+ } catch (e) {
103
+ // cross-origin or security restriction — safe to ignore
104
+ }
105
+ };
106
+
107
+ // Apply current theme as soon as the iframe content is ready
108
+ iframeEl.addEventListener('load', () => applyJsDocsTheme(darkTheme));
109
+
110
+ // Keep in sync whenever the parent page theme changes
111
+ ThemeEvents[swaggerThemeEventKey] = () => {
112
+ if (s(`.iframe-${ModalId}`)) applyJsDocsTheme(darkTheme);
113
+ };
114
+ }
115
+
116
+ if (type === 'api') {
117
+ swaggerThemeEventKey = `swagger-iframe-${ModalId}`;
118
+
119
+ const applySwaggerTheme = (isDark) => {
120
+ try {
121
+ const iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow?.document;
122
+ if (!iframeDoc || !iframeDoc.body) return;
123
+ if (isDark) {
124
+ iframeDoc.body.classList.add('swagger-dark');
125
+ } else {
126
+ iframeDoc.body.classList.remove('swagger-dark');
127
+ }
128
+ iframeEl.contentWindow?.localStorage?.setItem('swagger-theme', isDark ? 'dark' : 'light');
129
+ const toggleBtn = sIframe(iframeEl, '#swagger-theme-toggle');
130
+ if (toggleBtn) toggleBtn.textContent = isDark ? '\u2600\uFE0F Light Mode' : '\uD83C\uDF19 Dark Mode';
131
+ } catch (e) {
132
+ // cross-origin or security restriction — safe to ignore
133
+ }
134
+ };
135
+
136
+ // Apply current theme as soon as the iframe content is ready
137
+ iframeEl.addEventListener('load', () => applySwaggerTheme(darkTheme));
138
+
139
+ // Keep in sync whenever the parent page theme changes
140
+ ThemeEvents[swaggerThemeEventKey] = () => {
141
+ if (s(`.iframe-${ModalId}`)) applySwaggerTheme(darkTheme);
142
+ };
143
+ }
55
144
  }
56
145
  Modal.Data[ModalId].onObserverListener[ModalId] = () => {
57
146
  if (s(`.iframe-${ModalId}`)) {
@@ -67,6 +156,7 @@ const Docs = {
67
156
  };
68
157
  Modal.Data[ModalId].onObserverListener[ModalId]();
69
158
  Modal.Data[ModalId].onCloseListener[ModalId] = () => {
159
+ if (swaggerThemeEventKey) delete ThemeEvents[swaggerThemeEventKey];
70
160
  closeModalRouteChangeEvent({ closedId: ModalId });
71
161
  };
72
162
  },
@@ -342,10 +432,6 @@ const Docs = {
342
432
  <div class="docs-landing">
343
433
  <div class="docs-header">
344
434
  <h1>Documentation</h1>
345
- <p>
346
- Find everything you need to build amazing applications with our platform. Get started with our guides, API
347
- reference, and examples.
348
- </p>
349
435
  <!--
350
436
  <div class="search-bar">
351
437
  <i class="fas fa-search"></i>
@@ -418,12 +418,48 @@ function hexToRgbA(hex) {
418
418
 
419
419
  const htmlStrSanitize = (str) => (str ? str.replace(/<\/?[^>]+(>|$)/g, '').trim() : '');
420
420
 
421
+ /**
422
+ * Query selector inside an iframe. Allows obtaining a single element that is
423
+ * inside an iframe in order to execute events on it.
424
+ * Note: the iframe must be same-origin for this to work.
425
+ *
426
+ * @param {string|Element} iframeEl The CSS selector string or the iframe Element itself.
427
+ * @param {string} el The query selector for the element inside the iframe.
428
+ * @returns {Element|null} The matched element inside the iframe, or null if not found.
429
+ * @memberof VanillaJS
430
+ */
431
+ const sIframe = (iframeEl, el) => {
432
+ const iframe = typeof iframeEl === 'string' ? s(iframeEl) : iframeEl;
433
+ if (!iframe) return null;
434
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
435
+ return iframeDoc ? iframeDoc.querySelector(el) : null;
436
+ };
437
+
438
+ /**
439
+ * Query selector all inside an iframe. Allows obtaining all elements matching a
440
+ * selector that are inside an iframe in order to execute events on them.
441
+ * Note: the iframe must be same-origin for this to work.
442
+ *
443
+ * @param {string|Element} iframeEl The CSS selector string or the iframe Element itself.
444
+ * @param {string} el The query selector for the elements inside the iframe.
445
+ * @returns {NodeList|null} A NodeList of matched elements inside the iframe, or null if not found.
446
+ * @memberof VanillaJS
447
+ */
448
+ const saIframe = (iframeEl, el) => {
449
+ const iframe = typeof iframeEl === 'string' ? s(iframeEl) : iframeEl;
450
+ if (!iframe) return null;
451
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
452
+ return iframeDoc ? iframeDoc.querySelectorAll(el) : null;
453
+ };
454
+
421
455
  export {
422
456
  s,
423
457
  htmls,
424
458
  append,
425
459
  prepend,
426
460
  sa,
461
+ sIframe,
462
+ saIframe,
427
463
  copyData,
428
464
  pasteData,
429
465
  preHTML,
package/src/index.js CHANGED
@@ -42,7 +42,7 @@ class Underpost {
42
42
  * @type {String}
43
43
  * @memberof Underpost
44
44
  */
45
- static version = 'v3.0.0';
45
+ static version = 'v3.0.1';
46
46
 
47
47
  /**
48
48
  * Required Node.js major version
@@ -20,6 +20,7 @@ import { createPeerServer } from '../../server/peer.js';
20
20
  import { createValkeyConnection } from '../../server/valkey.js';
21
21
  import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
22
22
  import { ssrMiddlewareFactory } from '../../server/ssr.js';
23
+ import { buildSwaggerUiOptions } from '../../server/client-build-docs.js';
23
24
 
24
25
  import { shellExec } from '../../server/process.js';
25
26
  import { devProxyHostFactory, isDevProxyContext, isTlsDevProxy } from '../../server/conf.js';
@@ -167,8 +168,8 @@ class ExpressService {
167
168
  // Swagger UI setup
168
169
  if (fs.existsSync(swaggerJsonPath)) {
169
170
  const swaggerDoc = JSON.parse(fs.readFileSync(swaggerJsonPath, 'utf8'));
170
- // Reusing swaggerPath defined outside, removing unnecessary redeclaration
171
- app.use(swaggerPath, swaggerUi.serve, swaggerUi.setup(swaggerDoc));
171
+ const swaggerUiOptions = await buildSwaggerUiOptions();
172
+ app.use(swaggerPath, swaggerUi.serve, swaggerUi.setup(swaggerDoc, swaggerUiOptions));
172
173
  }
173
174
 
174
175
  // Security and CORS
@@ -10,6 +10,7 @@ import fs from 'fs-extra';
10
10
  import { shellExec } from './process.js';
11
11
  import { loggerFactory } from './logger.js';
12
12
  import { JSONweb } from './client-formatted.js';
13
+ import { ssrFactory } from './ssr.js';
13
14
 
14
15
  /**
15
16
  * Builds API documentation using Swagger
@@ -62,53 +63,91 @@ const buildApiDocs = async ({
62
63
  components: {
63
64
  schemas: {
64
65
  userRequest: {
65
- username: 'user123',
66
- password: 'Password123',
67
- email: 'user@example.com',
66
+ type: 'object',
67
+ required: ['username', 'password', 'email'],
68
+ properties: {
69
+ username: { type: 'string', example: 'user123' },
70
+ password: { type: 'string', example: 'Password123!' },
71
+ email: { type: 'string', format: 'email', example: 'user@example.com' },
72
+ },
68
73
  },
69
74
  userResponse: {
70
- status: 'success',
71
- data: {
72
- token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
73
- user: {
74
- _id: '66c377f57f99e5969b81de89',
75
- email: 'user@example.com',
76
- emailConfirmed: false,
77
- username: 'user123',
78
- role: 'user',
79
- profileImageId: '66c377f57f99e5969b81de87',
75
+ type: 'object',
76
+ properties: {
77
+ status: { type: 'string', example: 'success' },
78
+ data: {
79
+ type: 'object',
80
+ properties: {
81
+ token: {
82
+ type: 'string',
83
+ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
84
+ },
85
+ user: {
86
+ type: 'object',
87
+ properties: {
88
+ _id: { type: 'string', example: '66c377f57f99e5969b81de89' },
89
+ email: { type: 'string', format: 'email', example: 'user@example.com' },
90
+ emailConfirmed: { type: 'boolean', example: false },
91
+ username: { type: 'string', example: 'user123' },
92
+ role: { type: 'string', example: 'user' },
93
+ profileImageId: { type: 'string', example: '66c377f57f99e5969b81de87' },
94
+ },
95
+ },
96
+ },
80
97
  },
81
98
  },
82
99
  },
83
100
  userUpdateResponse: {
84
- status: 'success',
85
- data: {
86
- _id: '66c377f57f99e5969b81de89',
87
- email: 'user@example.com',
88
- emailConfirmed: false,
89
- username: 'user123222',
90
- role: 'user',
91
- profileImageId: '66c377f57f99e5969b81de87',
101
+ type: 'object',
102
+ properties: {
103
+ status: { type: 'string', example: 'success' },
104
+ data: {
105
+ type: 'object',
106
+ properties: {
107
+ _id: { type: 'string', example: '66c377f57f99e5969b81de89' },
108
+ email: { type: 'string', format: 'email', example: 'user@example.com' },
109
+ emailConfirmed: { type: 'boolean', example: false },
110
+ username: { type: 'string', example: 'user123222' },
111
+ role: { type: 'string', example: 'user' },
112
+ profileImageId: { type: 'string', example: '66c377f57f99e5969b81de87' },
113
+ },
114
+ },
92
115
  },
93
116
  },
94
117
  userGetResponse: {
95
- status: 'success',
96
- data: {
97
- _id: '66c377f57f99e5969b81de89',
98
- email: 'user@example.com',
99
- emailConfirmed: false,
100
- username: 'user123222',
101
- role: 'user',
102
- profileImageId: '66c377f57f99e5969b81de87',
118
+ type: 'object',
119
+ properties: {
120
+ status: { type: 'string', example: 'success' },
121
+ data: {
122
+ type: 'object',
123
+ properties: {
124
+ _id: { type: 'string', example: '66c377f57f99e5969b81de89' },
125
+ email: { type: 'string', format: 'email', example: 'user@example.com' },
126
+ emailConfirmed: { type: 'boolean', example: false },
127
+ username: { type: 'string', example: 'user123222' },
128
+ role: { type: 'string', example: 'user' },
129
+ profileImageId: { type: 'string', example: '66c377f57f99e5969b81de87' },
130
+ },
131
+ },
103
132
  },
104
133
  },
105
134
  userLogInRequest: {
106
- email: 'user@example.com',
107
- password: 'Password123',
135
+ type: 'object',
136
+ required: ['email', 'password'],
137
+ properties: {
138
+ email: { type: 'string', format: 'email', example: 'user@example.com' },
139
+ password: { type: 'string', example: 'Password123!' },
140
+ },
108
141
  },
109
142
  userBadRequestResponse: {
110
- status: 'error',
111
- message: 'Bad request. Please check your inputs, and try again',
143
+ type: 'object',
144
+ properties: {
145
+ status: { type: 'string', example: 'error' },
146
+ message: {
147
+ type: 'string',
148
+ example: 'Bad request. Please check your inputs, and try again',
149
+ },
150
+ },
112
151
  },
113
152
  },
114
153
  securitySchemes: {
@@ -120,6 +159,44 @@ const buildApiDocs = async ({
120
159
  },
121
160
  };
122
161
 
162
+ /**
163
+ * swagger-autogen has no requestBody annotation support — it only handles
164
+ * #swagger.parameters, responses, security, etc. We define the requestBody
165
+ * objects here and inject them into the generated JSON as a post-processing step.
166
+ *
167
+ * Each key is an "<method> <path>" pair matching the generated paths object.
168
+ * The value is a valid OAS 3.0 requestBody object.
169
+ */
170
+ const requestBodies = {
171
+ 'post /user': {
172
+ description: 'User registration data',
173
+ required: true,
174
+ content: {
175
+ 'application/json': {
176
+ schema: { $ref: '#/components/schemas/userRequest' },
177
+ },
178
+ },
179
+ },
180
+ 'post /user/auth': {
181
+ description: 'User login credentials',
182
+ required: true,
183
+ content: {
184
+ 'application/json': {
185
+ schema: { $ref: '#/components/schemas/userLogInRequest' },
186
+ },
187
+ },
188
+ },
189
+ 'put /user/{id}': {
190
+ description: 'User fields to update',
191
+ required: true,
192
+ content: {
193
+ 'application/json': {
194
+ schema: { $ref: '#/components/schemas/userRequest' },
195
+ },
196
+ },
197
+ },
198
+ };
199
+
123
200
  logger.warn('build swagger api docs', doc.info);
124
201
 
125
202
  // swagger-autogen@2.9.2 bug: getProducesTag, getConsumesTag, getResponsesTag missing __¬¬¬__ decode before eval
@@ -148,6 +225,33 @@ const buildApiDocs = async ({
148
225
  }
149
226
 
150
227
  await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
228
+
229
+ // Post-process: inject requestBody into operations — swagger-autogen silently
230
+ // ignores #swagger.requestBody annotations and has no internal OAS-3 body support.
231
+ if (fs.existsSync(outputFile)) {
232
+ const swaggerJson = JSON.parse(fs.readFileSync(outputFile, 'utf8'));
233
+ let patched = false;
234
+
235
+ for (const [key, requestBody] of Object.entries(requestBodies)) {
236
+ const [method, ...pathParts] = key.split(' ');
237
+ const opPath = pathParts.join(' ');
238
+ if (swaggerJson.paths?.[opPath]?.[method]) {
239
+ swaggerJson.paths[opPath][method].requestBody = requestBody;
240
+ // Remove any stale in:body entry from parameters (OAS 3.0 doesn't allow it)
241
+ if (Array.isArray(swaggerJson.paths[opPath][method].parameters)) {
242
+ swaggerJson.paths[opPath][method].parameters = swaggerJson.paths[opPath][method].parameters.filter(
243
+ (p) => p.in !== 'body',
244
+ );
245
+ }
246
+ patched = true;
247
+ }
248
+ }
249
+
250
+ if (patched) {
251
+ fs.writeFileSync(outputFile, JSON.stringify(swaggerJson, null, 2), 'utf8');
252
+ // logger.warn('swagger post-process: requestBody injected', Object.keys(requestBodies));
253
+ }
254
+ }
151
255
  });
152
256
  };
153
257
 
@@ -247,4 +351,18 @@ const buildDocs = async ({
247
351
  });
248
352
  };
249
353
 
250
- export { buildDocs };
354
+ /**
355
+ * Builds Swagger UI customization options by rendering the SwaggerDarkMode SSR body component.
356
+ * Returns the customCss and customJsStr strings required by swagger-ui-express to enable
357
+ * a dark/light mode toggle button with a black/gray gradient dark theme.
358
+ * @function buildSwaggerUiOptions
359
+ * @memberof clientBuildDocs
360
+ * @returns {Promise<{customCss: string, customJsStr: string}>} Swagger UI setup options
361
+ */
362
+ const buildSwaggerUiOptions = async () => {
363
+ const swaggerDarkMode = await ssrFactory('./src/client/ssr/body/SwaggerDarkMode.js');
364
+ const { css, js } = swaggerDarkMode();
365
+ return { customCss: css, customJsStr: js };
366
+ };
367
+
368
+ export { buildDocs, buildSwaggerUiOptions };