@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 +1 -2
- package/.env.test +1 -2
- package/.github/workflows/gitlab.ci.yml +20 -0
- package/CHANGELOG.md +50 -1
- package/CLI-HELP.md +1 -1
- package/README.md +2 -2
- package/bin/file.js +9 -3
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +5 -1
- package/src/api/user/user.router.js +0 -47
- package/src/cli/lxd.js +1 -1
- package/src/cli/run.js +3 -2
- package/src/client/components/core/Docs.js +92 -6
- package/src/client/components/core/VanillaJs.js +36 -0
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +3 -2
- package/src/server/client-build-docs.js +152 -34
package/.env.development
CHANGED
package/.env.test
CHANGED
|
@@ -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-
|
|
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
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<div align="center">
|
|
18
18
|
|
|
19
|
-
[](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [](https://www.npmjs.com/package/underpost) [](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [](https://www.npmjs.com/package/underpost) [](https://socket.dev/npm/package/underpost/overview/3.0.1) [](https://coveralls.io/github/underpostnet/engine?branch=master) [](https://www.npmjs.org/package/underpost) [](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.
|
|
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
|
|
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 =
|
|
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
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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 };
|