@wx-sab/renkei 1.3.4 → 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli.js +4 -2
- package/lib/commands/migrate.d.ts +4 -0
- package/lib/commands/migrate.js +20 -0
- package/lib/core/migrate.d.ts +13 -0
- package/lib/core/migrate.js +128 -0
- package/lib/core/migrate.test.d.ts +1 -0
- package/lib/core/migrate.test.js +95 -0
- package/package.json +1 -1
- package/templates/api.ejs +35 -37
- package/templates/procedure-call.ejs +2 -2
package/lib/cli.js
CHANGED
|
@@ -30,6 +30,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
30
30
|
const commander_1 = __importDefault(require("commander"));
|
|
31
31
|
const TSNode = __importStar(require("ts-node"));
|
|
32
32
|
const api_1 = __importDefault(require("./commands/api"));
|
|
33
|
+
const migrate_1 = __importDefault(require("./commands/migrate"));
|
|
33
34
|
TSNode.register({
|
|
34
35
|
// 不加载本地的 tsconfig.json
|
|
35
36
|
skipProject: true,
|
|
@@ -39,8 +40,8 @@ TSNode.register({
|
|
|
39
40
|
compilerOptions: {
|
|
40
41
|
strict: false,
|
|
41
42
|
target: "es6",
|
|
42
|
-
module: "
|
|
43
|
-
moduleResolution: "
|
|
43
|
+
module: "node16",
|
|
44
|
+
moduleResolution: "node16",
|
|
44
45
|
declaration: false,
|
|
45
46
|
removeComments: false,
|
|
46
47
|
esModuleInterop: true,
|
|
@@ -53,4 +54,5 @@ TSNode.register({
|
|
|
53
54
|
commander_1.default.program
|
|
54
55
|
.version(require("../package.json").version, "-v, --version")
|
|
55
56
|
.addCommand(api_1.default)
|
|
57
|
+
.addCommand(migrate_1.default)
|
|
56
58
|
.parse();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runMigrateCommand = void 0;
|
|
7
|
+
const commander_1 = __importDefault(require("commander"));
|
|
8
|
+
const migrate_1 = require("../core/migrate");
|
|
9
|
+
const runMigrateCommand = (dir, dry) => {
|
|
10
|
+
(0, migrate_1.migrateImports)(dir, dry);
|
|
11
|
+
};
|
|
12
|
+
exports.runMigrateCommand = runMigrateCommand;
|
|
13
|
+
const parse = new commander_1.default.Command("migrate")
|
|
14
|
+
.description("把 service 的具名 import 迁移为 namespace import,配合函数式产物做方法级 tree shaking(调用点 XxxService.method() 不变)")
|
|
15
|
+
.argument("[dir]", "要扫描的目录", "src")
|
|
16
|
+
.option("--dry", "只打印将要修改的内容,不写入文件", false)
|
|
17
|
+
.action((dir, options) => {
|
|
18
|
+
(0, exports.runMigrateCommand)(dir, options.dry);
|
|
19
|
+
});
|
|
20
|
+
exports.default = parse;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MigrateStats {
|
|
2
|
+
changed: number;
|
|
3
|
+
files: number;
|
|
4
|
+
skipped: number;
|
|
5
|
+
}
|
|
6
|
+
export type Logger = (message: string) => void;
|
|
7
|
+
/**
|
|
8
|
+
* 扫描目录,把 service 的具名 import 迁移为 namespace import。
|
|
9
|
+
* @param root 扫描根目录
|
|
10
|
+
* @param dry true 只打印不写入
|
|
11
|
+
* @param log 日志输出(默认 console.log),便于测试注入
|
|
12
|
+
*/
|
|
13
|
+
export declare function migrateImports(root: string, dry: boolean, log?: Logger): MigrateStats;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.migrateImports = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* 把 service 的具名 import 批量迁移为 namespace import,
|
|
9
|
+
* 配合函数式产物做方法级 tree shaking,调用点 XxxService.method() 写法不变。
|
|
10
|
+
*
|
|
11
|
+
* import { CopUpms } from "@/services/cop-upms"
|
|
12
|
+
* ↓
|
|
13
|
+
* import * as CopUpms from "@/services/cop-upms"
|
|
14
|
+
*
|
|
15
|
+
* 分流规则(只动 from 路径含 /services/ 的具名 import):
|
|
16
|
+
* - 大写开头单具名(旧 namespace 名,如 CopUpms) → 自动改 import * as
|
|
17
|
+
* - 小写开头单具名(扁平函数,如 post_xxx) → 忽略(已是函数式,无需迁移)
|
|
18
|
+
* - 多具名 / 带 as 别名 → 跳过并给出改法提示(确保不漏迁)
|
|
19
|
+
*
|
|
20
|
+
* 局限:基于正则,不感知注释/字符串,会一并改写注释里的 import 文本(对运行无影响)。
|
|
21
|
+
*/
|
|
22
|
+
const fs_1 = __importDefault(require("fs"));
|
|
23
|
+
const path_1 = __importDefault(require("path"));
|
|
24
|
+
const TARGET_EXT = new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".mjs"]);
|
|
25
|
+
const SKIP_DIR = new Set(["node_modules", "dist", "lib", "build", ".git"]);
|
|
26
|
+
// 抓所有 services 路径的具名 import,花括号内容(inner)另行判断
|
|
27
|
+
const SERVICES_IMPORT_RE = /import\s*\{\s*([^}]+?)\s*\}\s*from\s*(['"])([^'"]*\/services\/[^'"]+)\2/g;
|
|
28
|
+
const UPPER_SINGLE = /^[A-Z]\w*$/; // 大写开头单具名 → 旧 namespace 名,自动改
|
|
29
|
+
const LOWER_SINGLE = /^[a-z_$][\w$]*$/; // 小写开头单具名 → 扁平函数 import,忽略
|
|
30
|
+
function buildHint(inner, q, from) {
|
|
31
|
+
const asMatch = inner.match(/^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/);
|
|
32
|
+
if (asMatch) {
|
|
33
|
+
// 单具名 + as: import { CopUpms as Upms } → import * as Upms
|
|
34
|
+
return `建议改为: import * as ${asMatch[2]} from ${q}${from}${q}`;
|
|
35
|
+
}
|
|
36
|
+
if (inner.includes(",")) {
|
|
37
|
+
const names = inner.split(",").map((s) => s.trim());
|
|
38
|
+
const uppers = names.filter((n) => UPPER_SINGLE.test(n));
|
|
39
|
+
const lowers = names.filter((n) => LOWER_SINGLE.test(n));
|
|
40
|
+
const parts = [];
|
|
41
|
+
if (uppers.length)
|
|
42
|
+
parts.push(`import * as ${uppers[0]} from ${q}${from}${q}`);
|
|
43
|
+
if (lowers.length)
|
|
44
|
+
parts.push(`import { ${lowers.join(", ")} } from ${q}${from}${q}`);
|
|
45
|
+
if (!parts.length)
|
|
46
|
+
return `需手动拆分: ${names.join(", ")}`;
|
|
47
|
+
const tail = names.length === uppers.length + lowers.length ? "" : "(其余含 as/复杂项需手动)";
|
|
48
|
+
return `建议拆为: ${parts.join(" + ")} ${tail}`.trim();
|
|
49
|
+
}
|
|
50
|
+
return "需手动处理";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 扫描目录,把 service 的具名 import 迁移为 namespace import。
|
|
54
|
+
* @param root 扫描根目录
|
|
55
|
+
* @param dry true 只打印不写入
|
|
56
|
+
* @param log 日志输出(默认 console.log),便于测试注入
|
|
57
|
+
*/
|
|
58
|
+
function migrateImports(root, dry, log = console.log) {
|
|
59
|
+
const stats = { changed: 0, files: 0, skipped: 0 };
|
|
60
|
+
const walk = (dir) => {
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = fs_1.default.readdirSync(dir);
|
|
64
|
+
}
|
|
65
|
+
catch (_a) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (const name of entries) {
|
|
69
|
+
const p = path_1.default.join(dir, name);
|
|
70
|
+
let st;
|
|
71
|
+
try {
|
|
72
|
+
st = fs_1.default.statSync(p);
|
|
73
|
+
}
|
|
74
|
+
catch (_b) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (st.isDirectory()) {
|
|
78
|
+
if (SKIP_DIR.has(name))
|
|
79
|
+
continue;
|
|
80
|
+
walk(p);
|
|
81
|
+
}
|
|
82
|
+
else if (TARGET_EXT.has(path_1.default.extname(name))) {
|
|
83
|
+
const src = fs_1.default.readFileSync(p, "utf8");
|
|
84
|
+
SERVICES_IMPORT_RE.lastIndex = 0;
|
|
85
|
+
const fixes = [];
|
|
86
|
+
const skips = [];
|
|
87
|
+
let m;
|
|
88
|
+
while ((m = SERVICES_IMPORT_RE.exec(src))) {
|
|
89
|
+
const inner = (m[1] || "").trim();
|
|
90
|
+
const q = m[2];
|
|
91
|
+
const from = m[3];
|
|
92
|
+
if (UPPER_SINGLE.test(inner)) {
|
|
93
|
+
fixes.push({ name: inner, q, from, idx: m.index, raw: m[0] });
|
|
94
|
+
}
|
|
95
|
+
else if (LOWER_SINGLE.test(inner)) {
|
|
96
|
+
// 扁平函数 import,已是函数式,无需迁移
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
skips.push({ raw: m[0], hint: buildHint(inner, q, from) });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (fixes.length) {
|
|
103
|
+
stats.files++;
|
|
104
|
+
let out = src;
|
|
105
|
+
let offset = 0;
|
|
106
|
+
for (const r of fixes) {
|
|
107
|
+
const repl = `import * as ${r.name} from ${r.q}${r.from}${r.q}`;
|
|
108
|
+
out = out.slice(0, r.idx + offset) + repl + out.slice(r.idx + offset + r.raw.length);
|
|
109
|
+
offset += repl.length - r.raw.length;
|
|
110
|
+
stats.changed++;
|
|
111
|
+
log(`${dry ? "[dry] " : "[fix] "}${p}\n ${r.raw.trim()} → import * as ${r.name} from ${r.q}${r.from}${r.q}`);
|
|
112
|
+
}
|
|
113
|
+
if (!dry)
|
|
114
|
+
fs_1.default.writeFileSync(p, out);
|
|
115
|
+
}
|
|
116
|
+
for (const s of skips) {
|
|
117
|
+
stats.skipped++;
|
|
118
|
+
log(`${dry ? "[dry] " : "[skip]"}${p}\n ${s.raw.trim()}\n ${s.hint}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
walk(path_1.default.resolve(root));
|
|
124
|
+
log(`\n完成: 改写 ${stats.changed} 处 import(${stats.files} 个文件), 跳过 ${stats.skipped} 处需手动处理` +
|
|
125
|
+
`${dry ? " —— [dry-run 未写入]" : " —— 已写入"}`);
|
|
126
|
+
return stats;
|
|
127
|
+
}
|
|
128
|
+
exports.migrateImports = migrateImports;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const migrate_1 = require("./migrate");
|
|
11
|
+
let tmp;
|
|
12
|
+
(0, vitest_1.beforeEach)(() => {
|
|
13
|
+
tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "migrate-"));
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.afterEach)(() => {
|
|
16
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
const write = (file, content) => {
|
|
19
|
+
const p = path_1.default.join(tmp, file);
|
|
20
|
+
fs_1.default.mkdirSync(path_1.default.dirname(p), { recursive: true });
|
|
21
|
+
fs_1.default.writeFileSync(p, content);
|
|
22
|
+
};
|
|
23
|
+
const read = (file) => fs_1.default.readFileSync(path_1.default.join(tmp, file), "utf8");
|
|
24
|
+
const noLog = () => { };
|
|
25
|
+
(0, vitest_1.describe)("migrateImports", () => {
|
|
26
|
+
(0, vitest_1.it)("改写大写开头的单具名 service import", () => {
|
|
27
|
+
write("a.ts", 'import { CopUpms } from "@/services/cop-upms";\n');
|
|
28
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, noLog);
|
|
29
|
+
(0, vitest_1.expect)(stats.changed).toBe(1);
|
|
30
|
+
(0, vitest_1.expect)(read("a.ts")).toContain('import * as CopUpms from "@/services/cop-upms"');
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.it)("同一文件多处 import 都被改写", () => {
|
|
33
|
+
write("a.ts", 'import { CopUpms } from "@/services/cop-upms";\nimport { DpService } from "@/services/dp-service";\n');
|
|
34
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, noLog);
|
|
35
|
+
(0, vitest_1.expect)(stats.changed).toBe(2);
|
|
36
|
+
(0, vitest_1.expect)(read("a.ts")).toContain('import * as CopUpms from "@/services/cop-upms"');
|
|
37
|
+
(0, vitest_1.expect)(read("a.ts")).toContain('import * as DpService from "@/services/dp-service"');
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)("多具名跳过并给出拆分提示,不写入", () => {
|
|
40
|
+
const src = 'import { CopUpms, otherUtil } from "@/services/cop-upms";\n';
|
|
41
|
+
write("a.ts", src);
|
|
42
|
+
const logs = [];
|
|
43
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, (m) => logs.push(m));
|
|
44
|
+
(0, vitest_1.expect)(stats.changed).toBe(0);
|
|
45
|
+
(0, vitest_1.expect)(stats.skipped).toBe(1);
|
|
46
|
+
(0, vitest_1.expect)(read("a.ts")).toBe(src);
|
|
47
|
+
(0, vitest_1.expect)(logs.join("\n")).toContain("建议拆为");
|
|
48
|
+
(0, vitest_1.expect)(logs.join("\n")).toContain("otherUtil");
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)("as 别名跳过并给出 namespace 建议", () => {
|
|
51
|
+
write("a.ts", 'import { CopUpms as Upms } from "@/services/cop-upms";\n');
|
|
52
|
+
const logs = [];
|
|
53
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, (m) => logs.push(m));
|
|
54
|
+
(0, vitest_1.expect)(stats.skipped).toBe(1);
|
|
55
|
+
(0, vitest_1.expect)(stats.changed).toBe(0);
|
|
56
|
+
(0, vitest_1.expect)(logs.join("\n")).toContain("import * as Upms");
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.it)("忽略小写开头的函数式 import(已是函数式)", () => {
|
|
59
|
+
const src = 'import { post_account_roles } from "@/services/cop-upms";\n';
|
|
60
|
+
write("a.ts", src);
|
|
61
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, noLog);
|
|
62
|
+
(0, vitest_1.expect)(stats.changed).toBe(0);
|
|
63
|
+
(0, vitest_1.expect)(stats.skipped).toBe(0);
|
|
64
|
+
(0, vitest_1.expect)(read("a.ts")).toBe(src);
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.it)("忽略非 services 路径的 import", () => {
|
|
67
|
+
const src = 'import { someUtil } from "@/utils/helper";\n';
|
|
68
|
+
write("a.ts", src);
|
|
69
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, noLog);
|
|
70
|
+
(0, vitest_1.expect)(stats.changed).toBe(0);
|
|
71
|
+
(0, vitest_1.expect)(read("a.ts")).toBe(src);
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)("dry-run 不写入文件", () => {
|
|
74
|
+
const src = 'import { CopUpms } from "@/services/cop-upms";\n';
|
|
75
|
+
write("a.ts", src);
|
|
76
|
+
const stats = (0, migrate_1.migrateImports)(tmp, true, noLog);
|
|
77
|
+
(0, vitest_1.expect)(stats.changed).toBe(1);
|
|
78
|
+
(0, vitest_1.expect)(read("a.ts")).toBe(src);
|
|
79
|
+
});
|
|
80
|
+
(0, vitest_1.it)("跳过 node_modules / dist 等目录", () => {
|
|
81
|
+
write("node_modules/pkg/x.ts", 'import { CopUpms } from "@/services/cop-upms";\n');
|
|
82
|
+
write("dist/build.ts", 'import { CopUpms } from "@/services/cop-upms";\n');
|
|
83
|
+
write("src/a.ts", 'import { CopUpms } from "@/services/cop-upms";\n');
|
|
84
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, noLog);
|
|
85
|
+
(0, vitest_1.expect)(stats.changed).toBe(1);
|
|
86
|
+
(0, vitest_1.expect)(read("src/a.ts")).toContain("import * as CopUpms");
|
|
87
|
+
(0, vitest_1.expect)(read("node_modules/pkg/x.ts")).not.toContain("import * as");
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)("递归扫描子目录", () => {
|
|
90
|
+
write("src/views/user/a.ts", 'import { CopUpms } from "@/services/cop-upms";\n');
|
|
91
|
+
const stats = (0, migrate_1.migrateImports)(tmp, false, noLog);
|
|
92
|
+
(0, vitest_1.expect)(stats.changed).toBe(1);
|
|
93
|
+
(0, vitest_1.expect)(read("src/views/user/a.ts")).toContain("import * as CopUpms");
|
|
94
|
+
});
|
|
95
|
+
});
|
package/package.json
CHANGED
package/templates/api.ejs
CHANGED
|
@@ -56,47 +56,45 @@ enum ContentType {
|
|
|
56
56
|
UrlEncoded = 'application/x-www-form-urlencoded',
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
59
|
+
/**
|
|
60
|
+
* 常规 Mock 服务地址
|
|
61
|
+
*/
|
|
62
|
+
let mockService = '<%~ config.customMockService?.(config) || '' %>';
|
|
63
|
+
export function setMockService (service: string) {
|
|
64
|
+
mockService = service;
|
|
65
|
+
}
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
/**
|
|
68
|
+
* 开启Mock
|
|
69
|
+
*/
|
|
70
|
+
let isMock = false;
|
|
71
|
+
export function enableMock(enable: boolean = true) {
|
|
72
|
+
isMock = enable;
|
|
73
|
+
}
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
return params.path;
|
|
75
|
+
/**
|
|
76
|
+
* 如果开启 mock, 替换请求的url
|
|
77
|
+
* @param {String} path 请求接口路径
|
|
78
|
+
* @param {Boolean} mock 是否开启 Mock
|
|
79
|
+
* @param {String} customMockUrl 自定义mock链接
|
|
80
|
+
*/
|
|
81
|
+
function generateUrl(params: {
|
|
82
|
+
path: string,
|
|
83
|
+
mock?: boolean,
|
|
84
|
+
customMockUrl?: string
|
|
85
|
+
}): string {
|
|
86
|
+
if (isMock || params.mock) {
|
|
87
|
+
return params.customMockUrl || (mockService + params.path)
|
|
91
88
|
}
|
|
89
|
+
return params.path;
|
|
90
|
+
}
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<% }) %>
|
|
92
|
+
<% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %>
|
|
93
|
+
<% routes.forEach((route) => { %>
|
|
94
|
+
<% if (route.routeName.usage !== SKIP_ROUTE_MARKER) { %>
|
|
95
|
+
<%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
|
|
96
|
+
<% } %>
|
|
99
97
|
<% }) %>
|
|
100
|
-
}
|
|
98
|
+
<% }) %>
|
|
101
99
|
|
|
102
100
|
export { <%~ modelNamespace %> }
|
|
@@ -157,8 +157,8 @@ const wrappedDataArgs = _
|
|
|
157
157
|
<%~ routeDocs.lines %>
|
|
158
158
|
|
|
159
159
|
*/
|
|
160
|
-
|
|
161
|
-
const url =
|
|
160
|
+
export const <%~ route.routeName.usage %> = async (<%~ wrappedDataArgs %>): Promise<<%~ getWrapperFullType(type) %>> => {
|
|
161
|
+
const url = generateUrl({
|
|
162
162
|
path: `<%~ path %>`,
|
|
163
163
|
mock: config.mock,
|
|
164
164
|
customMockUrl: `<%~ route.raw.mockUrl || '' %>`
|