ptech-preset 1.3.1 → 1.3.3
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/dist/index.js +120 -128
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/index.ts
|
|
2
9
|
import path3 from "path";
|
|
3
10
|
import fs3 from "fs";
|
|
@@ -127,6 +134,14 @@ async function collectAutoRemotes(opts = {}) {
|
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
// src/index.ts
|
|
137
|
+
var DEFAULT_SHARED = {
|
|
138
|
+
react: { singleton: true, eager: true, requiredVersion: false },
|
|
139
|
+
"react-dom": { singleton: true, eager: true, requiredVersion: false },
|
|
140
|
+
"react-router": { singleton: true, eager: true, requiredVersion: false },
|
|
141
|
+
"@azure/msal-react": { singleton: true, eager: true, requiredVersion: false },
|
|
142
|
+
"@azure/msal-browser": { singleton: true, eager: true, requiredVersion: false }
|
|
143
|
+
};
|
|
144
|
+
var CDN_BASE = "https://oneportal.blob.core.windows.net/external/ts/";
|
|
130
145
|
function getPackageName(root) {
|
|
131
146
|
try {
|
|
132
147
|
const pkgPath = path3.resolve(root, "package.json");
|
|
@@ -148,22 +163,6 @@ function normalizeExposePath(root, maybeRel) {
|
|
|
148
163
|
return `./${relFromRoot}`;
|
|
149
164
|
}
|
|
150
165
|
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
151
|
-
var DEFAULT_SHARED = {
|
|
152
|
-
react: { singleton: true, eager: true, requiredVersion: false },
|
|
153
|
-
"react-dom": {
|
|
154
|
-
singleton: true,
|
|
155
|
-
eager: true,
|
|
156
|
-
requiredVersion: false
|
|
157
|
-
},
|
|
158
|
-
"react-router": { singleton: true, eager: true, requiredVersion: false },
|
|
159
|
-
"@azure/msal-react": { singleton: true, eager: true, requiredVersion: false },
|
|
160
|
-
"@azure/msal-browser": {
|
|
161
|
-
singleton: true,
|
|
162
|
-
eager: true,
|
|
163
|
-
requiredVersion: false
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
var CDN_BASE = "https://oneportal.blob.core.windows.net/external/ts/";
|
|
167
166
|
function parseRemoteSpec(key, val) {
|
|
168
167
|
if (typeof val !== "string") return null;
|
|
169
168
|
const m = val.match(/^([^@]+)@(.+)$/);
|
|
@@ -193,12 +192,6 @@ function buildRemoteTypeUrls(remotes) {
|
|
|
193
192
|
}
|
|
194
193
|
return out;
|
|
195
194
|
}
|
|
196
|
-
function ensureDtsObject(dts) {
|
|
197
|
-
if (dts === false) return false;
|
|
198
|
-
if (dts == null || dts === true) return {};
|
|
199
|
-
if (typeof dts === "object") return { ...dts };
|
|
200
|
-
return {};
|
|
201
|
-
}
|
|
202
195
|
async function devFetchRemoteTypesOnce(params) {
|
|
203
196
|
const { root, baseDir, typesFolderRel, urls } = params;
|
|
204
197
|
if (!urls || Object.keys(urls).length === 0) return;
|
|
@@ -209,9 +202,7 @@ async function devFetchRemoteTypesOnce(params) {
|
|
|
209
202
|
try {
|
|
210
203
|
const res = await fetch(spec.api);
|
|
211
204
|
if (!res.ok) {
|
|
212
|
-
console.warn(
|
|
213
|
-
`[mf-auto] fetch types failed for ${scope}: ${res.status} ${res.statusText}`
|
|
214
|
-
);
|
|
205
|
+
console.warn(`[mf-auto] fetch types failed for ${scope}: ${res.status} ${res.statusText}`);
|
|
215
206
|
continue;
|
|
216
207
|
}
|
|
217
208
|
const txt = await res.text();
|
|
@@ -223,11 +214,7 @@ async function devFetchRemoteTypesOnce(params) {
|
|
|
223
214
|
const refs = Object.keys(urls).map((s) => `/// <reference path="./__remotes/${s}.d.ts" />`).join(os.EOL);
|
|
224
215
|
const banner = `// generated by plugin-mf-auto (dev once)
|
|
225
216
|
`;
|
|
226
|
-
fs3.writeFileSync(
|
|
227
|
-
path3.join(typesRootAbs, "index.d.ts"),
|
|
228
|
-
banner + refs + os.EOL,
|
|
229
|
-
"utf8"
|
|
230
|
-
);
|
|
217
|
+
fs3.writeFileSync(path3.join(typesRootAbs, "index.d.ts"), banner + refs + os.EOL, "utf8");
|
|
231
218
|
const shimDir = path3.join(root, baseDir, ".mf-auto");
|
|
232
219
|
fs3.mkdirSync(shimDir, { recursive: true });
|
|
233
220
|
const relToIndex = path3.relative(shimDir, path3.join(typesRootAbs, "index.d.ts")).replace(/\\/g, "/");
|
|
@@ -239,44 +226,95 @@ async function devFetchRemoteTypesOnce(params) {
|
|
|
239
226
|
"utf8"
|
|
240
227
|
);
|
|
241
228
|
}
|
|
229
|
+
function tryRequireTs(root) {
|
|
230
|
+
const paths = [root, __dirname, process.cwd()];
|
|
231
|
+
for (const p of paths) {
|
|
232
|
+
try {
|
|
233
|
+
const tsPath = __require.resolve("typescript", { paths: [p] });
|
|
234
|
+
return __require(tsPath);
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
function readBaseTsConfigJSONC(baseAbs, root) {
|
|
241
|
+
const ts = tryRequireTs(root);
|
|
242
|
+
if (ts) {
|
|
243
|
+
try {
|
|
244
|
+
const r = ts.readConfigFile(baseAbs, ts.sys.readFile);
|
|
245
|
+
if (r.error) {
|
|
246
|
+
const msg = r.error.messageText && r.error.messageText.toString() || "unknown";
|
|
247
|
+
console.warn("[plugin-mf-auto] TypeScript readConfigFile error:", msg);
|
|
248
|
+
}
|
|
249
|
+
if (r.config) return r.config;
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.warn("[plugin-mf-auto] TS readConfigFile threw, fallback to JSONC:", e);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const { parse } = __require("jsonc-parser");
|
|
256
|
+
const raw = fs3.readFileSync(baseAbs, "utf8");
|
|
257
|
+
return parse(raw);
|
|
258
|
+
} catch {
|
|
259
|
+
try {
|
|
260
|
+
const raw = fs3.readFileSync(baseAbs, "utf8").replace(/^\uFEFF/, "").replace(/(^|[\s{[,])\/\/.*$/gm, "$1").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,(?=\s*[\]}])/g, "");
|
|
261
|
+
return JSON.parse(raw);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
console.warn("[plugin-mf-auto] Cannot parse tsconfig (fallback) -> using minimal:", e);
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function ensureDtsObject(dts) {
|
|
269
|
+
if (dts === false) return false;
|
|
270
|
+
if (dts == null || dts === true) return {};
|
|
271
|
+
if (typeof dts === "object") return { ...dts };
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
242
274
|
function writePatchedTsconfig(opts) {
|
|
243
275
|
const { root, baseTsconfigPath, typesFolder, remoteAliases, baseDir } = opts;
|
|
244
|
-
const
|
|
245
|
-
fs3.mkdirSync(
|
|
276
|
+
const outDirAbs = path3.resolve(root, ".mf-auto");
|
|
277
|
+
fs3.mkdirSync(outDirAbs, { recursive: true });
|
|
246
278
|
const baseAbs = path3.resolve(root, baseTsconfigPath);
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
const raw = fs3.readFileSync(baseAbs, "utf8").replace(/^\uFEFF/, "").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
250
|
-
base = JSON.parse(raw);
|
|
251
|
-
} catch (e) {
|
|
252
|
-
console.warn("[plugin-mf-auto] Cannot read base tsconfig, using minimal:", e);
|
|
253
|
-
base = {};
|
|
254
|
-
}
|
|
279
|
+
const base = readBaseTsConfigJSONC(baseAbs, root);
|
|
255
280
|
const compilerOptions = { ...base.compilerOptions ?? {} };
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
compilerOptions.
|
|
281
|
+
const rootAbs = path3.resolve(root).replace(/\\/g, "/");
|
|
282
|
+
const baseUrlAbs = (compilerOptions.baseUrl ? path3.resolve(root, compilerOptions.baseUrl) : rootAbs).replace(/\\/g, "/");
|
|
283
|
+
const srcAbs = path3.resolve(root, baseDir).replace(/\\/g, "/");
|
|
284
|
+
const typesAbs = path3.resolve(root, typesFolder).replace(/\\/g, "/");
|
|
285
|
+
compilerOptions.rootDir = rootAbs;
|
|
286
|
+
compilerOptions.baseUrl = baseUrlAbs;
|
|
287
|
+
compilerOptions.jsx ??= "react-jsx";
|
|
288
|
+
compilerOptions.moduleResolution ??= "bundler";
|
|
289
|
+
compilerOptions.module ??= "ESNext";
|
|
290
|
+
compilerOptions.target ??= "ES2020";
|
|
291
|
+
compilerOptions.lib ??= ["DOM", "ES2020"];
|
|
292
|
+
compilerOptions.skipLibCheck ??= true;
|
|
293
|
+
compilerOptions.resolveJsonModule ??= true;
|
|
294
|
+
compilerOptions.allowSyntheticDefaultImports ??= true;
|
|
295
|
+
compilerOptions.esModuleInterop ??= true;
|
|
296
|
+
compilerOptions.verbatimModuleSyntax ??= true;
|
|
261
297
|
const paths = { ...compilerOptions.paths ?? {} };
|
|
262
298
|
for (const scope of remoteAliases) {
|
|
263
299
|
const key = `${scope}/*`;
|
|
264
|
-
const val = `${
|
|
300
|
+
const val = `${typesAbs}/${scope}/*`;
|
|
265
301
|
const arr = Array.isArray(paths[key]) ? paths[key] : paths[key] ? [paths[key]] : [];
|
|
266
302
|
if (!arr.includes(val)) arr.push(val);
|
|
267
303
|
paths[key] = arr;
|
|
268
304
|
}
|
|
269
305
|
compilerOptions.paths = paths;
|
|
270
306
|
const tr = new Set(Array.isArray(compilerOptions.typeRoots) ? compilerOptions.typeRoots : []);
|
|
271
|
-
if (![...tr].some((s) => /node_modules\/@types$/.test(s))) tr.add(`${
|
|
307
|
+
if (![...tr].some((s) => /node_modules\/@types$/.test(s))) tr.add(`${rootAbs}/node_modules/@types`);
|
|
272
308
|
compilerOptions.typeRoots = Array.from(tr);
|
|
273
309
|
base.compilerOptions = compilerOptions;
|
|
274
310
|
const include = new Set(Array.isArray(base.include) ? base.include : []);
|
|
275
|
-
|
|
276
|
-
include.add(`${
|
|
277
|
-
include.add("
|
|
311
|
+
include.add(`${srcAbs}/**/*`);
|
|
312
|
+
include.add(`${typesAbs}/**/*`);
|
|
313
|
+
include.add(`${outDirAbs.replace(/\\/g, "/")}/**/*`);
|
|
278
314
|
base.include = Array.from(include);
|
|
279
|
-
|
|
315
|
+
delete base.files;
|
|
316
|
+
delete base.references;
|
|
317
|
+
const outPath = path3.join(outDirAbs, "tsconfig.for-dts.json");
|
|
280
318
|
fs3.writeFileSync(outPath, JSON.stringify(base, null, 2), "utf8");
|
|
281
319
|
return outPath;
|
|
282
320
|
}
|
|
@@ -298,19 +336,15 @@ function pluginCore(opts) {
|
|
|
298
336
|
mf
|
|
299
337
|
} = opts;
|
|
300
338
|
if (!mf || typeof mf !== "object") {
|
|
301
|
-
throw new Error(
|
|
302
|
-
'[plugin-mf-auto] "mf" options is required and must be an object.'
|
|
303
|
-
);
|
|
339
|
+
throw new Error('[plugin-mf-auto] "mf" options is required and must be an object.');
|
|
304
340
|
}
|
|
305
341
|
return {
|
|
306
342
|
name: "plugin-mf-auto",
|
|
307
343
|
async setup(api) {
|
|
308
344
|
const { pluginModuleFederation } = await import("@module-federation/rsbuild-plugin");
|
|
309
345
|
const getExposes = async () => {
|
|
310
|
-
if (exposesMode === "jsdoc")
|
|
311
|
-
|
|
312
|
-
if (exposesMode === "wrapper")
|
|
313
|
-
return collectAutoExposesWithWrapper({ baseDir, globs });
|
|
346
|
+
if (exposesMode === "jsdoc") return collectAutoExposes({ baseDir, globs });
|
|
347
|
+
if (exposesMode === "wrapper") return collectAutoExposesWithWrapper({ baseDir, globs });
|
|
314
348
|
const a = await collectAutoExposes({ baseDir, globs });
|
|
315
349
|
const b = await collectAutoExposesWithWrapper({ baseDir, globs });
|
|
316
350
|
return { ...a, ...b };
|
|
@@ -353,22 +387,11 @@ function pluginCore(opts) {
|
|
|
353
387
|
fs3.mkdirSync(tempDir, { recursive: true });
|
|
354
388
|
if (cssInjection === "wrapper" && mfMerged.exposes) {
|
|
355
389
|
const wrapped = {};
|
|
356
|
-
for (const [key, rel] of Object.entries(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const targetAbs = path3.resolve(
|
|
360
|
-
root,
|
|
361
|
-
String(rel).replace(/^\.\//, "")
|
|
362
|
-
);
|
|
363
|
-
const proxyFile = path3.join(
|
|
364
|
-
tempDir,
|
|
365
|
-
`expose_${key.replace(/[./]/g, "_")}.ts`
|
|
366
|
-
);
|
|
390
|
+
for (const [key, rel] of Object.entries(mfMerged.exposes)) {
|
|
391
|
+
const targetAbs = path3.resolve(root, String(rel).replace(/^\.\//, ""));
|
|
392
|
+
const proxyFile = path3.join(tempDir, `expose_${key.replace(/[./]/g, "_")}.ts`);
|
|
367
393
|
const lines = [];
|
|
368
|
-
if (hasCss)
|
|
369
|
-
lines.push(
|
|
370
|
-
`import ${JSON.stringify(cssAbs.replace(/\\/g, "/"))};`
|
|
371
|
-
);
|
|
394
|
+
if (hasCss) lines.push(`import ${JSON.stringify(cssAbs.replace(/\\/g, "/"))};`);
|
|
372
395
|
else lines.push(`/* no css: ${cssEntry} not found */`);
|
|
373
396
|
const target = JSON.stringify(targetAbs.replace(/\\/g, "/"));
|
|
374
397
|
lines.push(`export * from ${target};`);
|
|
@@ -384,17 +407,12 @@ function pluginCore(opts) {
|
|
|
384
407
|
const stylesFile = path3.join(tempDir, `styles_expose.ts`);
|
|
385
408
|
fs3.writeFileSync(
|
|
386
409
|
stylesFile,
|
|
387
|
-
`import ${JSON.stringify(
|
|
388
|
-
cssAbs.replace(/\\/g, "/")
|
|
389
|
-
)};
|
|
410
|
+
`import ${JSON.stringify(cssAbs.replace(/\\/g, "/"))};
|
|
390
411
|
// generated by plugin-mf-auto`,
|
|
391
412
|
"utf8"
|
|
392
413
|
);
|
|
393
414
|
const relFromRoot = path3.relative(root, stylesFile).replace(/\\/g, "/");
|
|
394
|
-
mfMerged.exposes = {
|
|
395
|
-
...mfMerged.exposes ?? {},
|
|
396
|
-
[stylesExposeKey]: `./${relFromRoot}`
|
|
397
|
-
};
|
|
415
|
+
mfMerged.exposes = { ...mfMerged.exposes ?? {}, [stylesExposeKey]: `./${relFromRoot}` };
|
|
398
416
|
}
|
|
399
417
|
}
|
|
400
418
|
const command = api?.context?.command;
|
|
@@ -402,65 +420,41 @@ function pluginCore(opts) {
|
|
|
402
420
|
const isDev = !isProdLike;
|
|
403
421
|
const autoTypeUrls = buildRemoteTypeUrls(mfMerged.remotes || {});
|
|
404
422
|
const remoteAliases = Object.keys(autoTypeUrls);
|
|
405
|
-
const baseTsconfigPath = "./tsconfig.json";
|
|
406
423
|
const tsconfigForDtsAbs = writePatchedTsconfig({
|
|
407
424
|
root,
|
|
408
425
|
baseTsconfigPath: "./tsconfig.json",
|
|
409
426
|
typesFolder,
|
|
410
|
-
remoteAliases
|
|
427
|
+
remoteAliases,
|
|
411
428
|
baseDir
|
|
412
429
|
});
|
|
413
430
|
const overrideTsCfg = process.env.MF_DTS_TSCONFIG;
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
tsConfigPath: tsconfigForDtsRel
|
|
419
|
-
};
|
|
420
|
-
mfMerged.dts = dtsObj;
|
|
431
|
+
const tsconfigForDtsToUse = (() => {
|
|
432
|
+
if (!overrideTsCfg) return tsconfigForDtsAbs;
|
|
433
|
+
return path3.isAbsolute(overrideTsCfg) ? overrideTsCfg : path3.resolve(root, overrideTsCfg);
|
|
434
|
+
})().replace(/\\/g, "/");
|
|
421
435
|
if (isDev) {
|
|
422
436
|
const refresh = process.env[devTypesRefreshEnvVar] === "1";
|
|
423
437
|
const typesRootAbs = path3.resolve(root, typesFolder);
|
|
424
438
|
const indexFile = path3.join(typesRootAbs, "index.d.ts");
|
|
425
|
-
const needFetch = refresh || !fs3.existsSync(indexFile) ||
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (Object.keys(autoTypeUrls).length > 0 && needFetch) {
|
|
429
|
-
await devFetchRemoteTypesOnce({
|
|
430
|
-
root,
|
|
431
|
-
baseDir,
|
|
432
|
-
typesFolderRel: typesFolder,
|
|
433
|
-
urls: autoTypeUrls
|
|
434
|
-
});
|
|
439
|
+
const needFetch = refresh || !fs3.existsSync(indexFile) || remoteAliases.some((s) => !fs3.existsSync(path3.join(typesRootAbs, "__remotes", `${s}.d.ts`)));
|
|
440
|
+
if (remoteAliases.length > 0 && needFetch) {
|
|
441
|
+
await devFetchRemoteTypesOnce({ root, baseDir, typesFolderRel: typesFolder, urls: autoTypeUrls });
|
|
435
442
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
443
|
+
}
|
|
444
|
+
const dtsGen = ensureDtsObject(mfMerged.dts);
|
|
445
|
+
dtsGen.generateTypes = { ...dtsGen.generateTypes ?? {}, tsConfigPath: tsconfigForDtsToUse };
|
|
446
|
+
if (!isDev && remoteAliases.length > 0) {
|
|
447
|
+
const existingConsume = dtsGen.consumeTypes ?? {};
|
|
448
|
+
const existingRt = existingConsume.remoteTypeUrls ?? {};
|
|
449
|
+
dtsGen.consumeTypes = {
|
|
450
|
+
typesFolder: existingConsume.typesFolder ?? typesFolder,
|
|
451
|
+
maxRetries: existingConsume.maxRetries ?? 3,
|
|
452
|
+
remoteTypeUrls: { ...autoTypeUrls, ...existingRt }
|
|
440
453
|
};
|
|
441
|
-
mfMerged.dts = dtsObj2;
|
|
442
|
-
} else {
|
|
443
|
-
if (mfMerged.dts !== false) {
|
|
444
|
-
const dtsObj2 = ensureDtsObject(mfMerged.dts);
|
|
445
|
-
if (Object.keys(autoTypeUrls).length > 0) {
|
|
446
|
-
const existingConsume = dtsObj2.consumeTypes ?? {};
|
|
447
|
-
const existingRt = existingConsume.remoteTypeUrls ?? {};
|
|
448
|
-
dtsObj2.consumeTypes = {
|
|
449
|
-
typesFolder: existingConsume.typesFolder ?? typesFolder,
|
|
450
|
-
maxRetries: existingConsume.maxRetries ?? 3,
|
|
451
|
-
// ưu tiên config hiện hữu nếu trùng key
|
|
452
|
-
remoteTypeUrls: { ...autoTypeUrls, ...existingRt }
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
dtsObj2.generateTypes = {
|
|
456
|
-
...dtsObj2.generateTypes ?? {},
|
|
457
|
-
tsConfigPath: tsconfigForDtsRel
|
|
458
|
-
// dùng tsconfig đã patch từ tsconfig của app
|
|
459
|
-
};
|
|
460
|
-
mfMerged.dts = dtsObj2;
|
|
461
|
-
}
|
|
462
454
|
}
|
|
463
|
-
|
|
455
|
+
mfMerged.dts = dtsGen;
|
|
456
|
+
const { pluginModuleFederation: mfPluginFactory } = await import("@module-federation/rsbuild-plugin");
|
|
457
|
+
const mfPlugin = mfPluginFactory(mfMerged);
|
|
464
458
|
await mfPlugin.setup?.(api);
|
|
465
459
|
const pkgSafe = getPackageName(root) || mfMerged.name || path3.basename(root).replace(/\W+/g, "");
|
|
466
460
|
api.modifyRsbuildConfig((config) => {
|
|
@@ -476,9 +470,7 @@ function pluginCore(opts) {
|
|
|
476
470
|
});
|
|
477
471
|
if (separateExposes && mfMerged.exposes) {
|
|
478
472
|
const force = {};
|
|
479
|
-
for (const [key, rel] of Object.entries(
|
|
480
|
-
mfMerged.exposes
|
|
481
|
-
)) {
|
|
473
|
+
for (const [key, rel] of Object.entries(mfMerged.exposes)) {
|
|
482
474
|
const abs = path3.resolve(root, String(rel).replace(/^\.\//, "")).replace(/\\/g, "/");
|
|
483
475
|
const safe = separateExposeChunkPrefix + key.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
484
476
|
force[safe] = new RegExp(`${escapeRegExp(abs)}$`);
|