underpost 3.2.70 → 3.2.90
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/.github/workflows/publish.ci.yml +3 -3
- package/.github/workflows/release.cd.yml +1 -1
- package/CHANGELOG.md +1358 -1038
- package/CLI-HELP.md +39 -16
- package/README.md +3 -3
- package/bin/build.js +10 -4
- package/bin/deploy.js +18 -16
- package/docker-compose.yml +1 -1
- 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/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/package.json +20 -20
- package/scripts/nat-iptables.sh +10 -4
- package/scripts/test-monitor.sh +4 -3
- package/src/api/core/core.controller.js +4 -65
- package/src/api/core/core.router.js +8 -14
- package/src/api/default/default.controller.js +2 -70
- package/src/api/default/default.router.js +7 -17
- package/src/api/document/document.controller.js +5 -77
- package/src/api/document/document.router.js +9 -13
- package/src/api/file/file.controller.js +9 -53
- package/src/api/file/file.router.js +14 -6
- package/src/api/test/test.controller.js +8 -53
- package/src/api/test/test.router.js +1 -4
- package/src/cli/cluster.js +771 -66
- package/src/cli/db.js +6 -4
- package/src/cli/deploy.js +1715 -168
- package/src/cli/docker-compose.js +19 -24
- package/src/cli/fs.js +0 -1
- package/src/cli/image.js +40 -13
- package/src/cli/index.js +129 -35
- package/src/cli/ipfs.js +82 -11
- package/src/cli/monitor.js +1 -1
- package/src/cli/release.js +4 -0
- package/src/cli/repository.js +14 -3
- package/src/cli/run.js +2253 -439
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +38 -39
- package/src/client/components/core/Modal.js +38 -4
- package/src/client-builder/client-build.js +94 -11
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +47 -32
- package/src/index.js +1 -1
- package/src/server/conf.js +1307 -6
- package/src/server/cri.js +70 -0
- package/src/server/downloader.js +3 -3
- package/src/server/middlewares.js +152 -0
- package/src/server/underpost-gateway.js +1073 -0
- package/src/server/underpost-ingress.js +364 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +469 -0
- package/test/underpost-ingress.test.js +253 -0
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { registerCrudRoutes } from '../../server/middlewares.js';
|
|
1
3
|
import { adminGuard } from '../../server/auth.js';
|
|
2
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
3
4
|
import { CoreController } from './core.controller.js';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
|
|
6
|
-
const logger = loggerFactory(import.meta);
|
|
7
5
|
|
|
8
6
|
class CoreRouter {
|
|
9
7
|
/**
|
|
@@ -11,16 +9,12 @@ class CoreRouter {
|
|
|
11
9
|
* @returns {import('express').Router}
|
|
12
10
|
*/
|
|
13
11
|
static router(options) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
router.put(`/`, options.authMiddleware, adminGuard, async (req, res) => await CoreController.put(req, res, options));
|
|
21
|
-
router.delete(`/:id`, options.authMiddleware, adminGuard, async (req, res) => await CoreController.delete(req, res, options));
|
|
22
|
-
router.delete(`/`, options.authMiddleware, adminGuard, async (req, res) => await CoreController.delete(req, res, options));
|
|
23
|
-
return router;
|
|
12
|
+
const adminOnly = [options.authMiddleware, adminGuard];
|
|
13
|
+
return registerCrudRoutes(express.Router(), CoreController, options, {
|
|
14
|
+
readGuards: adminOnly,
|
|
15
|
+
writeGuards: adminOnly,
|
|
16
|
+
deleteAllGuards: adminOnly,
|
|
17
|
+
});
|
|
24
18
|
}
|
|
25
19
|
}
|
|
26
20
|
|
|
@@ -1,74 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildCrudController } from '../../server/middlewares.js';
|
|
2
2
|
import { DefaultService } from './default.service.js';
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
class DefaultController {
|
|
7
|
-
static post = async (req, res, options) => {
|
|
8
|
-
try {
|
|
9
|
-
const result = await DefaultService.post(req, res, options);
|
|
10
|
-
return res.status(200).json({
|
|
11
|
-
status: 'success',
|
|
12
|
-
data: result,
|
|
13
|
-
});
|
|
14
|
-
} catch (error) {
|
|
15
|
-
logger.error(error, error.stack);
|
|
16
|
-
return res.status(400).json({
|
|
17
|
-
status: 'error',
|
|
18
|
-
message: error.message,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
static get = async (req, res, options) => {
|
|
23
|
-
try {
|
|
24
|
-
const { page, limit } = req.query;
|
|
25
|
-
const result = await DefaultService.get(
|
|
26
|
-
{ ...req, query: { ...req.query, page: parseInt(page), limit: parseInt(limit) } },
|
|
27
|
-
res,
|
|
28
|
-
options,
|
|
29
|
-
);
|
|
30
|
-
return res.status(200).json({
|
|
31
|
-
status: 'success',
|
|
32
|
-
data: result,
|
|
33
|
-
});
|
|
34
|
-
} catch (error) {
|
|
35
|
-
logger.error(error, error.stack);
|
|
36
|
-
return res.status(400).json({
|
|
37
|
-
status: 'error',
|
|
38
|
-
message: error.message,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
static put = async (req, res, options) => {
|
|
43
|
-
try {
|
|
44
|
-
const result = await DefaultService.put(req, res, options);
|
|
45
|
-
return res.status(200).json({
|
|
46
|
-
status: 'success',
|
|
47
|
-
data: result,
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error(error, error.stack);
|
|
51
|
-
return res.status(400).json({
|
|
52
|
-
status: 'error',
|
|
53
|
-
message: error.message,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
static delete = async (req, res, options) => {
|
|
58
|
-
try {
|
|
59
|
-
const result = await DefaultService.delete(req, res, options);
|
|
60
|
-
return res.status(200).json({
|
|
61
|
-
status: 'success',
|
|
62
|
-
data: result,
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
logger.error(error, error.stack);
|
|
66
|
-
return res.status(400).json({
|
|
67
|
-
status: 'error',
|
|
68
|
-
message: error.message,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
4
|
+
const DefaultController = buildCrudController(DefaultService);
|
|
73
5
|
|
|
74
6
|
export { DefaultController };
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
2
|
-
import { DefaultController } from './default.controller.js';
|
|
3
1
|
import express from 'express';
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { registerCrudRoutes } from '../../server/middlewares.js';
|
|
3
|
+
import { DefaultController } from './default.controller.js';
|
|
6
4
|
|
|
7
5
|
class DefaultRouter {
|
|
8
6
|
/**
|
|
@@ -10,19 +8,11 @@ class DefaultRouter {
|
|
|
10
8
|
* @returns {import('express').Router}
|
|
11
9
|
*/
|
|
12
10
|
static router(options) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async (req, res) => await DefaultController.get(req, res, options),
|
|
19
|
-
);
|
|
20
|
-
router.get(`/`, async (req, res) => await DefaultController.get(req, res, options));
|
|
21
|
-
router.put(`/:id`, async (req, res) => await DefaultController.put(req, res, options));
|
|
22
|
-
router.put(`/`, async (req, res) => await DefaultController.put(req, res, options));
|
|
23
|
-
router.delete(`/:id`, async (req, res) => await DefaultController.delete(req, res, options));
|
|
24
|
-
router.delete(`/`, async (req, res) => await DefaultController.delete(req, res, options));
|
|
25
|
-
return router;
|
|
11
|
+
// Fully unguarded (matches prior behavior).
|
|
12
|
+
return registerCrudRoutes(express.Router(), DefaultController, options, {
|
|
13
|
+
writeGuards: [],
|
|
14
|
+
deleteAllGuards: [],
|
|
15
|
+
});
|
|
26
16
|
}
|
|
27
17
|
}
|
|
28
18
|
|
|
@@ -1,81 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildCrudController, serviceHandler } from '../../server/middlewares.js';
|
|
2
2
|
import { DocumentService } from './document.service.js';
|
|
3
|
-
const logger = loggerFactory(import.meta);
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
status: 'success',
|
|
10
|
-
data: await DocumentService.post(req, res, options),
|
|
11
|
-
});
|
|
12
|
-
} catch (error) {
|
|
13
|
-
logger.error(error, error.stack);
|
|
14
|
-
return res.status(400).json({
|
|
15
|
-
status: 'error',
|
|
16
|
-
message: error.message,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
static get = async (req, res, options) => {
|
|
21
|
-
try {
|
|
22
|
-
return res.status(200).json({
|
|
23
|
-
status: 'success',
|
|
24
|
-
data: await DocumentService.get(req, res, options),
|
|
25
|
-
});
|
|
26
|
-
} catch (error) {
|
|
27
|
-
logger.error(error, error.stack);
|
|
28
|
-
return res.status(400).json({
|
|
29
|
-
status: 'error',
|
|
30
|
-
message: error.message,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
static delete = async (req, res, options) => {
|
|
35
|
-
try {
|
|
36
|
-
const result = await DocumentService.delete(req, res, options);
|
|
37
|
-
return res.status(200).json({
|
|
38
|
-
status: 'success',
|
|
39
|
-
data: result,
|
|
40
|
-
});
|
|
41
|
-
} catch (error) {
|
|
42
|
-
logger.error(error, error.stack);
|
|
43
|
-
return res.status(400).json({
|
|
44
|
-
status: 'error',
|
|
45
|
-
message: error.message,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
static put = async (req, res, options) => {
|
|
50
|
-
try {
|
|
51
|
-
const result = await DocumentService.put(req, res, options);
|
|
52
|
-
return res.status(200).json({
|
|
53
|
-
status: 'success',
|
|
54
|
-
data: result,
|
|
55
|
-
});
|
|
56
|
-
} catch (error) {
|
|
57
|
-
logger.error(error, error.stack);
|
|
58
|
-
return res.status(400).json({
|
|
59
|
-
status: 'error',
|
|
60
|
-
message: error.message,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
static patch = async (req, res, options) => {
|
|
65
|
-
try {
|
|
66
|
-
const result = await DocumentService.patch(req, res, options);
|
|
67
|
-
return res.status(200).json({
|
|
68
|
-
status: 'success',
|
|
69
|
-
data: result,
|
|
70
|
-
});
|
|
71
|
-
} catch (error) {
|
|
72
|
-
logger.error(error, error.stack);
|
|
73
|
-
return res.status(400).json({
|
|
74
|
-
status: 'error',
|
|
75
|
-
message: error.message,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
4
|
+
const DocumentController = buildCrudController(DocumentService, {
|
|
5
|
+
get: serviceHandler(DocumentService.get),
|
|
6
|
+
patch: serviceHandler(DocumentService.patch),
|
|
7
|
+
});
|
|
80
8
|
|
|
81
9
|
export { DocumentController };
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
2
|
-
import { DocumentController } from './document.controller.js';
|
|
3
1
|
import express from 'express';
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { registerCrudRoutes } from '../../server/middlewares.js';
|
|
3
|
+
import { DocumentController } from './document.controller.js';
|
|
6
4
|
|
|
7
5
|
class DocumentRouter {
|
|
8
6
|
/**
|
|
@@ -11,23 +9,21 @@ class DocumentRouter {
|
|
|
11
9
|
*/
|
|
12
10
|
static router(options) {
|
|
13
11
|
const router = express.Router();
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const authOnly = [options.authMiddleware];
|
|
13
|
+
// Public listings — must come before the guarded generic /:id routes.
|
|
16
14
|
router.get(`/public/high`, async (req, res) => await DocumentController.get(req, res, options));
|
|
17
15
|
router.get(`/public`, async (req, res) => await DocumentController.get(req, res, options));
|
|
18
|
-
router.get(`/:id`, options.authMiddleware, async (req, res) => await DocumentController.get(req, res, options));
|
|
19
|
-
router.get(`/`, options.authMiddleware, async (req, res) => await DocumentController.get(req, res, options));
|
|
20
|
-
router.put(`/:id`, options.authMiddleware, async (req, res) => await DocumentController.put(req, res, options));
|
|
21
|
-
router.put(`/`, options.authMiddleware, async (req, res) => await DocumentController.put(req, res, options));
|
|
22
16
|
router.patch(`/:id/copy-share-link`, async (req, res) => await DocumentController.patch(req, res, options));
|
|
23
17
|
router.patch(
|
|
24
18
|
`/:id/toggle-public`,
|
|
25
19
|
options.authMiddleware,
|
|
26
20
|
async (req, res) => await DocumentController.patch(req, res, options),
|
|
27
21
|
);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
return registerCrudRoutes(router, DocumentController, options, {
|
|
23
|
+
readGuards: authOnly,
|
|
24
|
+
writeGuards: authOnly,
|
|
25
|
+
deleteAllGuards: authOnly,
|
|
26
|
+
});
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -1,59 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { controllerHandler, sendSuccess, serviceHandler, setCrossOriginHeaders } from '../../server/middlewares.js';
|
|
2
2
|
import { FileService } from './file.service.js';
|
|
3
|
-
const logger = loggerFactory(import.meta);
|
|
4
3
|
|
|
5
4
|
class FileController {
|
|
6
|
-
static post =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return res.status(400).json({
|
|
15
|
-
status: 'error',
|
|
16
|
-
message: error.message,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
static get = async (req, res, options) => {
|
|
21
|
-
try {
|
|
22
|
-
if (req && req.headers && req.headers.origin) {
|
|
23
|
-
res.set('Access-Control-Allow-Origin', req.headers.origin);
|
|
24
|
-
} else res.setHeader('Access-Control-Allow-Origin', '*');
|
|
25
|
-
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
26
|
-
const result = await FileService.get(req, res, options);
|
|
27
|
-
if (result instanceof Buffer) {
|
|
28
|
-
return res.status(200).end(result);
|
|
29
|
-
}
|
|
30
|
-
return res.status(200).json({
|
|
31
|
-
status: 'success',
|
|
32
|
-
data: result,
|
|
33
|
-
});
|
|
34
|
-
} catch (error) {
|
|
35
|
-
logger.error(error, error.stack);
|
|
36
|
-
return res.status(400).json({
|
|
37
|
-
status: 'error',
|
|
38
|
-
message: error.message,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
static delete = async (req, res, options) => {
|
|
43
|
-
try {
|
|
44
|
-
const result = await FileService.delete(req, res, options);
|
|
45
|
-
return res.status(200).json({
|
|
46
|
-
status: 'success',
|
|
47
|
-
data: result,
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error(error, error.stack);
|
|
51
|
-
return res.status(400).json({
|
|
52
|
-
status: 'error',
|
|
53
|
-
message: error.message,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
};
|
|
5
|
+
static post = serviceHandler(FileService.post);
|
|
6
|
+
static get = controllerHandler(async (req, res, options) => {
|
|
7
|
+
setCrossOriginHeaders(req, res);
|
|
8
|
+
const result = await FileService.get(req, res, options);
|
|
9
|
+
if (result instanceof Buffer) return res.status(200).end(result);
|
|
10
|
+
return sendSuccess(res, result);
|
|
11
|
+
});
|
|
12
|
+
static delete = serviceHandler(FileService.delete);
|
|
57
13
|
}
|
|
58
14
|
|
|
59
15
|
export { FileController };
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import express from 'express';
|
|
1
2
|
import { adminGuard } from '../../server/auth.js';
|
|
2
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
3
3
|
import { FileController } from './file.controller.js';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
|
|
6
|
-
const logger = loggerFactory(import.meta);
|
|
7
4
|
|
|
8
5
|
class FileRouter {
|
|
9
6
|
/**
|
|
7
|
+
* authenticated writes, admin-only deletes.
|
|
10
8
|
* @param {import('../types.js').RouterOptions} options
|
|
11
9
|
* @returns {import('express').Router}
|
|
12
10
|
*/
|
|
@@ -17,8 +15,18 @@ class FileRouter {
|
|
|
17
15
|
router.get(`/blob/:id`, async (req, res) => await FileController.get(req, res, options));
|
|
18
16
|
router.get(`/:id`, async (req, res) => await FileController.get(req, res, options));
|
|
19
17
|
router.get(`/`, async (req, res) => await FileController.get(req, res, options));
|
|
20
|
-
router.delete(
|
|
21
|
-
|
|
18
|
+
router.delete(
|
|
19
|
+
`/:id`,
|
|
20
|
+
options.authMiddleware,
|
|
21
|
+
adminGuard,
|
|
22
|
+
async (req, res) => await FileController.delete(req, res, options),
|
|
23
|
+
);
|
|
24
|
+
router.delete(
|
|
25
|
+
`/`,
|
|
26
|
+
options.authMiddleware,
|
|
27
|
+
adminGuard,
|
|
28
|
+
async (req, res) => await FileController.delete(req, res, options),
|
|
29
|
+
);
|
|
22
30
|
return router;
|
|
23
31
|
}
|
|
24
32
|
}
|
|
@@ -1,59 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { controllerHandler, sendSuccess, serviceHandler } from '../../server/middlewares.js';
|
|
2
2
|
import { TestService } from './test.service.js';
|
|
3
3
|
|
|
4
|
-
const logger = loggerFactory(import.meta);
|
|
5
|
-
|
|
6
4
|
class TestController {
|
|
7
|
-
static post =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logger.error(error, error.stack);
|
|
15
|
-
return res.status(400).json({
|
|
16
|
-
status: 'error',
|
|
17
|
-
message: error.message,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
static get = async (req, res, options) => {
|
|
22
|
-
try {
|
|
23
|
-
const result = await TestService.get(req, res, options);
|
|
24
|
-
if (result)
|
|
25
|
-
return res.status(200).json({
|
|
26
|
-
status: 'success',
|
|
27
|
-
data: result,
|
|
28
|
-
});
|
|
29
|
-
else
|
|
30
|
-
return res.status(400).json({
|
|
31
|
-
status: 'error',
|
|
32
|
-
});
|
|
33
|
-
} catch (error) {
|
|
34
|
-
logger.error(error, error.stack);
|
|
35
|
-
return res.status(400).json({
|
|
36
|
-
status: 'error',
|
|
37
|
-
message: error.message,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
static delete = async (req, res, options) => {
|
|
42
|
-
try {
|
|
43
|
-
const result = await TestService.delete(req, res, options);
|
|
44
|
-
|
|
45
|
-
return res.status(200).json({
|
|
46
|
-
status: 'success',
|
|
47
|
-
data: result,
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error(error, error.stack);
|
|
51
|
-
return res.status(400).json({
|
|
52
|
-
status: 'error',
|
|
53
|
-
message: error.message,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
};
|
|
5
|
+
static post = serviceHandler(TestService.post);
|
|
6
|
+
static get = controllerHandler(async (req, res, options) => {
|
|
7
|
+
const result = await TestService.get(req, res, options);
|
|
8
|
+
if (result) return sendSuccess(res, result);
|
|
9
|
+
return res.status(400).json({ status: 'error' });
|
|
10
|
+
});
|
|
11
|
+
static delete = serviceHandler(TestService.delete);
|
|
57
12
|
}
|
|
58
13
|
|
|
59
14
|
export { TestController };
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
2
|
-
import { TestController } from './test.controller.js';
|
|
3
1
|
import express from 'express';
|
|
4
|
-
|
|
5
|
-
const logger = loggerFactory(import.meta);
|
|
2
|
+
import { TestController } from './test.controller.js';
|
|
6
3
|
|
|
7
4
|
class TestRouter {
|
|
8
5
|
/**
|