@yeepay/client-utils 3.0.6 → 3.1.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/README.md CHANGED
@@ -41,8 +41,9 @@ const service = serviceFactory(
41
41
  {
42
42
  baseUrl: '/xxx-server',
43
43
  headers: {},
44
- // glob mock 数据文件位置
45
- mockModules: import.meta.glob('./mock/**/*.json', { eager: true }),
44
+ mock: {
45
+ modules: import.meta.glob('./mock/**/*.json', { eager: true }),
46
+ },
46
47
  },
47
48
  successCallback,
48
49
  failCallback,
@@ -81,3 +82,34 @@ export default (config: AxiosRequestConfig) => {
81
82
  }, { 'new-header-hello': 'world' }]
82
83
  }
83
84
  ```
85
+
86
+ ### Debug 日志打印
87
+
88
+ 开发者常常使用 console.log 日志打印来调试代码,且往往会忘记删除这些日志,导致线上环境也打印日志,影响性能与潜在安全风险。
89
+
90
+ `@yeepay/client-utils` 提供了 debug 模块来帮助开发者更好地控制日志打印。
91
+
92
+ #### 基本使用
93
+
94
+ ```javascript
95
+ import { debug } from '@yeepay/client-utils'
96
+
97
+ debug.page1('page1 is loaded')
98
+
99
+ debug.api('fetch user api is called')
100
+ ```
101
+
102
+ ![image](http://gitlab.yeepay.com/fex/client-utils/-/raw/master/static/debug-demo.png)
103
+
104
+ 这些日志默认在开发环境打印,而在生产环境不打印。
105
+
106
+ 如果想要在生产环境打印,可以在浏览器控制台执行:
107
+
108
+ ```javascript
109
+ debug.enableAll()
110
+ // 或者打印特定模块
111
+ debug.enable('page1')
112
+
113
+ // 恢复默认行为
114
+ debug.disable()
115
+ ```
package/dist/index.d.ts CHANGED
@@ -1,5 +1,64 @@
1
+ import debug from "debug";
1
2
  import { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from "axios";
2
3
 
4
+ //#region src/debug.d.ts
5
+ declare const logger: Record<string, debug.Debugger> & debug.Debugger;
6
+ declare global {
7
+ interface Window {
8
+ debug: {
9
+ enableAll: () => void;
10
+ enable: (namespace: string) => void;
11
+ disable: () => void;
12
+ };
13
+ }
14
+ }
15
+ //#endregion
16
+ //#region src/request/mock.d.ts
17
+ /**
18
+ * A valid `picomatch` glob pattern, or array of patterns.
19
+ */
20
+ type FilterPattern = ReadonlyArray<string | RegExp> | string | RegExp | null;
21
+ interface Mock {
22
+ /**
23
+ * Default is enable localhost domain, is disabled at other domains.
24
+ *
25
+ * @default undefined
26
+ */
27
+ enabled?: boolean;
28
+ /**
29
+ * Response delay in milliseconds.
30
+ *
31
+ * @default 200
32
+ */
33
+ delay?: number;
34
+ /**
35
+ * The Modules that store mock data.
36
+ *
37
+ * Please use glob patterns to specify the modules.
38
+ *
39
+ * @default {}
40
+ */
41
+ modules?: Record<string, unknown | (() => Promise<unknown>)>;
42
+ /**
43
+ * If enabled log in console.
44
+ *
45
+ * @default true
46
+ */
47
+ log?: boolean;
48
+ /**
49
+ * The Modules to include the mock.
50
+ *
51
+ * @default ['*']
52
+ */
53
+ includes?: FilterPattern;
54
+ /**
55
+ * The Modules to exclude the mock.
56
+ *
57
+ * @default []
58
+ */
59
+ excludes?: FilterPattern;
60
+ }
61
+ //#endregion
3
62
  //#region src/request/index.d.ts
4
63
  interface ServiceFactoryOptions {
5
64
  baseUrl?: string;
@@ -7,7 +66,7 @@ interface ServiceFactoryOptions {
7
66
  headers?: Record<string, string>;
8
67
  code?: string | number;
9
68
  debug?: boolean;
10
- mockModules?: Record<string, unknown | (() => Promise<unknown>)>;
69
+ mock?: Mock;
11
70
  }
12
71
  interface ServiceFactoryCallbacks {
13
72
  successCallback?: (response: AxiosResponse<any>) => any;
@@ -34,4 +93,4 @@ declare function removeToken(): void;
34
93
  //#region src/utils.d.ts
35
94
  declare function getQueryObject(url?: string): Record<string, string>;
36
95
  //#endregion
37
- export { getQueryObject, getToken, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
96
+ export { logger as debug, getQueryObject, getToken, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
package/dist/index.js CHANGED
@@ -1,9 +1,60 @@
1
- import { toArray } from "@imyangyong/utils";
1
+ import debug from "debug";
2
+ import { interopDefault, toArray } from "@imyangyong/utils";
2
3
  import axios from "axios";
3
4
  import { bold, cyan, gray, green, red, yellow } from "ansis";
4
5
  import AxiosMockAdapter from "axios-mock-adapter";
6
+ import picomatch from "picomatch/posix";
5
7
  import Cookie from "js-cookie";
6
8
 
9
+ //#region src/debug.ts
10
+ function createLogger(namespace) {
11
+ return debug(namespace);
12
+ }
13
+ const loggers = {};
14
+ const logger = new Proxy(function() {}, {
15
+ get(_, prop) {
16
+ if (prop in loggers) return loggers[prop];
17
+ const newLogger = createLogger(`${prop}`);
18
+ loggers[prop] = newLogger;
19
+ return newLogger;
20
+ },
21
+ apply(_, thisArg, args) {
22
+ return (loggers.app || (loggers.app = createLogger("app"))).apply(thisArg, args);
23
+ }
24
+ });
25
+ window.debug = {
26
+ enableAll: () => {
27
+ localStorage.setItem("debug", "*");
28
+ location.reload();
29
+ },
30
+ enable: (namespace) => {
31
+ if (!namespace) {
32
+ window.debug.enableAll();
33
+ return;
34
+ }
35
+ const namespaces = (localStorage.getItem("debug") || "").split(",").filter(Boolean);
36
+ namespaces.push(`${namespace}`);
37
+ localStorage.setItem("debug", namespaces.join(","));
38
+ location.reload();
39
+ },
40
+ disable: () => {
41
+ try {
42
+ if (process?.env?.NODE_ENV === "development") localStorage.setItem("debug", "null");
43
+ else localStorage.removeItem("debug");
44
+ } finally {
45
+ location.reload();
46
+ }
47
+ }
48
+ };
49
+ try {
50
+ if (process.env.NODE_ENV === "development") {
51
+ if (!localStorage.getItem("debug")) window.debug.enableAll();
52
+ }
53
+ } catch (error) {
54
+ console.error("Error enabling logger:", error);
55
+ }
56
+
57
+ //#endregion
7
58
  //#region src/request/mock.ts
8
59
  /**
9
60
  * 查找数组中最长相同前缀连续子序列
@@ -20,32 +71,62 @@ function longestCommonPrefix(strs) {
20
71
  function isLocalhost() {
21
72
  return window.location.hostname === "localhost";
22
73
  }
23
- async function setupMockAdapter(axiosInstance, mockModules) {
24
- if (!mockModules) return;
25
- if (!isLocalhost()) return;
26
- const mock = new AxiosMockAdapter(axiosInstance);
27
- const paths = Object.keys(mockModules);
28
- const longestPrefix = longestCommonPrefix(paths);
29
- paths.forEach((path) => {
74
+ /**
75
+ * Simple pattern matching function that handles basic glob patterns and regex
76
+ */
77
+ function matchesPattern(str, pattern) {
78
+ if (pattern instanceof RegExp) return pattern.test(str);
79
+ if (typeof pattern === "string") {
80
+ if (pattern === "*") return true;
81
+ return picomatch(pattern)(str);
82
+ }
83
+ return false;
84
+ }
85
+ /**
86
+ * Check if a uri should be included based on includes/excludes patterns
87
+ */
88
+ function shouldIncludeModule(uri, includes, excludes) {
89
+ const includePatterns = includes ? Array.isArray(includes) ? includes : [includes] : ["*"];
90
+ if ((excludes ? Array.isArray(excludes) ? excludes : [excludes] : []).some((pattern) => pattern && matchesPattern(uri, pattern))) return false;
91
+ return includePatterns.some((pattern) => pattern && matchesPattern(uri, pattern));
92
+ }
93
+ async function setupMockAdapter(axiosInstance, mock) {
94
+ if (!mock) return;
95
+ const { enabled, delay = 200, modules, includes = ["*"], excludes = [], log = true } = mock;
96
+ if (enabled === void 0 && !isLocalhost() || enabled === false) return;
97
+ if (!modules) return;
98
+ const mockAdapter = new AxiosMockAdapter(axiosInstance, {
99
+ delayResponse: delay,
100
+ onNoMatch: "passthrough"
101
+ });
102
+ const allPaths = Object.keys(modules);
103
+ const longestPrefix = longestCommonPrefix(allPaths);
104
+ const filteredPaths = allPaths.map((path) => {
30
105
  const longestPrefixIndex = longestPrefix.lastIndexOf("/");
31
106
  const dotIndex = path.lastIndexOf(".");
32
- const uri = path.slice(longestPrefixIndex, dotIndex);
107
+ return {
108
+ uri: path.slice(longestPrefixIndex, dotIndex),
109
+ path
110
+ };
111
+ }).filter(({ uri }) => shouldIncludeModule(uri, includes, excludes));
112
+ if (filteredPaths.length === 0) return;
113
+ filteredPaths.forEach(({ uri, path }) => {
33
114
  const registerMock = (mod) => {
34
- const replyFunc = typeof mod.default === "function" ? mod.default : () => [200, mod.default];
35
- mock.onAny(new RegExp(uri)).reply((config) => {
115
+ const replyFunc = typeof mod === "function" ? mod : () => [200, mod];
116
+ mockAdapter.onAny(new RegExp(uri)).reply((config) => {
36
117
  const response = replyFunc(config);
37
118
  const status = String(response[0]);
38
119
  const statusColor = status.startsWith("2") ? green(status) : status.startsWith("3") ? yellow(status) : red(status);
39
120
  const data = response[1] ? JSON.stringify(response[1], null, 2) : "";
40
121
  const headers = response[2] ? JSON.stringify(response[2], null, 2) : "";
41
- console.log(`${gray("Mocked:")} ${bold("URI")}: ${cyan.bold(uri)} \n${bold("Status")}: ${statusColor} \n${bold("Response")}: ${data} \n${headers ? `${bold("Headers:")} ${headers}` : ""}`);
122
+ if (log) console.log(`${gray("Mocked:")} ${bold("URI")}: ${cyan.bold(uri)} \n${bold("Status")}: ${statusColor} \n${bold("Response")}: ${data} \n${headers ? `${bold("Headers:")} ${headers}` : ""}`);
42
123
  return response;
43
124
  });
44
125
  };
45
- if (typeof mockModules[path] === "function") mockModules[path]().then(registerMock).catch((error) => {
126
+ if (typeof modules[path] === "function") interopDefault(modules[path]()).then(registerMock).catch((error) => {
46
127
  console.error(`@yeepay/client-utils mock trying to loading ${path} failed`, error);
47
128
  });
48
- else registerMock(mockModules[path]);
129
+ else registerMock(modules[path]);
49
130
  });
50
131
  }
51
132
 
@@ -55,8 +136,7 @@ function getQueryObject(url) {
55
136
  url = !url ? window.location.href : url;
56
137
  const search = url.substring(url.lastIndexOf("?") + 1);
57
138
  const obj = {};
58
- const reg = /([^?&=]+)=([^?&=]*)/g;
59
- search.replace(reg, (rs, $1, $2) => {
139
+ search.replace(/([^?&=]+)=([^?&=]*)/g, (rs, $1, $2) => {
60
140
  const name = decodeURIComponent($1);
61
141
  let val = decodeURIComponent($2);
62
142
  val = String(val);
@@ -128,7 +208,7 @@ function blobToJson(blob) {
128
208
  });
129
209
  }
130
210
  function serviceFactory(options, ...callbacks) {
131
- const { baseUrl = "/", timeout = 12e5, headers, code, debug, mockModules } = options;
211
+ const { baseUrl = "/", timeout = 12e5, headers, code, debug: debug$1, mock } = options;
132
212
  const { successCallback = (_response) => ({}), failCallback = (_error) => ({}), unauthorizedCallback = (_response) => ({}), forbiddenCallback = (_response) => ({}), notfoundCallback = (_response) => ({}), requestInterceptor = (c) => c, responseInterceptor = (r) => r } = compatibleCallback(callbacks);
133
213
  const service = axios.create({
134
214
  baseURL: baseUrl,
@@ -149,7 +229,7 @@ function serviceFactory(options, ...callbacks) {
149
229
  config.headers.Authorization = getToken();
150
230
  const { method, url = "" } = config;
151
231
  if (method === "get") {
152
- if (!debug) if (url.includes("?")) config.url = `${url}&_t=${(/* @__PURE__ */ new Date()).getTime()}`;
232
+ if (!debug$1) if (url.includes("?")) config.url = `${url}&_t=${(/* @__PURE__ */ new Date()).getTime()}`;
153
233
  else config.url = `${url}?_t=${(/* @__PURE__ */ new Date()).getTime()}`;
154
234
  }
155
235
  return config;
@@ -202,9 +282,9 @@ function serviceFactory(options, ...callbacks) {
202
282
  service.interceptors.response.use(responseInterceptor, (error) => {
203
283
  return Promise.reject(error);
204
284
  });
205
- if (mockModules) setupMockAdapter(service, mockModules);
285
+ if (mock) setupMockAdapter(service, mock);
206
286
  return service;
207
287
  }
208
288
 
209
289
  //#endregion
210
- export { getQueryObject, getToken, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
290
+ export { logger as debug, getQueryObject, getToken, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yeepay/client-utils",
3
3
  "type": "module",
4
- "version": "3.0.6",
4
+ "version": "3.1.1",
5
5
  "description": "shared utilities for yeepay client packages",
6
6
  "author": "Yong Yang",
7
7
  "homepage": "http://gitlab.yeepay.com/ued/client-utils#readme",
@@ -21,18 +21,24 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@imyangyong/utils": "^0.8.0",
24
+ "@yeepay/client-utils": "link:",
24
25
  "ansis": "^4.1.0",
25
26
  "axios": "^1.7.0",
26
27
  "axios-mock-adapter": "^2.1.0",
27
- "js-cookie": "^3.0.5"
28
+ "debug": "^4.4.3",
29
+ "js-cookie": "^3.0.5",
30
+ "picomatch": "^4.0.3"
28
31
  },
29
32
  "devDependencies": {
30
33
  "@antfu/ni": "^0.21.12",
34
+ "@types/debug": "^4.1.12",
31
35
  "@types/js-cookie": "^3.0.6",
36
+ "@types/picomatch": "^4.0.2",
32
37
  "@yeepay/eslint-config": "^4.17.0",
33
38
  "bumpp": "^9.4.1",
34
39
  "eslint": "^9.32.0",
35
40
  "eslint-plugin-format": "^0.1.1",
41
+ "happy-dom": "^18.0.1",
36
42
  "lint-staged": "^15.2.2",
37
43
  "simple-git-hooks": "^2.11.1",
38
44
  "tsdown": "^0.14.1",
@@ -51,6 +57,7 @@
51
57
  "watch": "tsdown --watch",
52
58
  "lint": "eslint .",
53
59
  "release": "bumpp && pnpm publish",
54
- "test": "vitest"
60
+ "test": "vitest",
61
+ "play": "npm -C playground run dev"
55
62
  }
56
63
  }