com.jimuwd.xian.registry-proxy 1.1.0 → 1.1.2

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import http, { createServer } from 'node:http';
3
3
  import https, { createServer as createHttpsServer } from 'node:https';
4
- import { promises as fsPromises, readFileSync } from 'node:fs';
4
+ import { existsSync, promises as fsPromises, readFileSync } from 'node:fs';
5
5
  import { load } from 'js-yaml';
6
6
  import fetch from 'node-fetch';
7
7
  import { homedir } from 'os';
@@ -11,6 +11,7 @@ import logger from "../utils/logger.js";
11
11
  import ConcurrencyLimiter from "../utils/ConcurrencyLimiter.js";
12
12
  import { gracefulShutdown, registerProcessShutdownHook } from "./gracefullShutdown.js";
13
13
  import { writePortFile } from "../port.js";
14
+ import resolveEnvValue from "../utils/resolveEnvValue.js";
14
15
  const { readFile } = fsPromises;
15
16
  const limiter = new ConcurrencyLimiter(Infinity);
16
17
  function removeEndingSlashAndForceStartingSlash(str) {
@@ -57,6 +58,11 @@ function removeRegistryPrefix(tarballUrl, registries) {
57
58
  }
58
59
  throw new Error(`Can't find tarball url ${tarballUrl} does not match given registries ${normalizedRegistries}`);
59
60
  }
61
+ /**
62
+ * 读取yml配置文件得到配置值对象{@link ProxyConfig}
63
+ * @note 本读取操作不会解析环境变量值
64
+ * @param proxyConfigPath 配置文件路径
65
+ */
60
66
  async function readProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
61
67
  let config = undefined;
62
68
  const resolvedPath = resolvePath(proxyConfigPath);
@@ -74,10 +80,20 @@ async function readProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
74
80
  }
75
81
  return config;
76
82
  }
83
+ /**
84
+ * 读取yml配置文件为yml对象
85
+ * @param path yml文件路径
86
+ */
77
87
  async function readYarnConfig(path) {
78
88
  try {
79
- const content = await readFile(resolvePath(path), 'utf8');
80
- return load(content);
89
+ if (existsSync(path)) {
90
+ const content = await readFile(resolvePath(path), 'utf8');
91
+ return load(content);
92
+ }
93
+ else {
94
+ logger.info(`Skip reading ${path}, because it does not exist.`);
95
+ return {};
96
+ }
81
97
  }
82
98
  catch (e) {
83
99
  logger.warn(`Failed to load Yarn config from ${path}:`, e);
@@ -93,7 +109,7 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
93
109
  const registryMap = new Map();
94
110
  for (const [proxiedRegUrl, proxyRegConfig] of Object.entries(proxyConfig.registries)) {
95
111
  const normalizedProxiedRegUrl = normalizeUrl(proxiedRegUrl);
96
- let token = proxyRegConfig?.npmAuthToken;
112
+ let token = resolveEnvValue(proxyRegConfig?.npmAuthToken);
97
113
  if (!token) {
98
114
  const yarnConfigs = [localYarnConfig, globalYarnConfig];
99
115
  for (const yarnConfig of yarnConfigs) {
@@ -103,6 +119,7 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
103
119
  if (foundEntry) {
104
120
  const [, registryConfig] = foundEntry;
105
121
  if (registryConfig?.npmAuthToken) {
122
+ // .yarnrc.yml内的配置值,暂时不处理环境变量值,未来按需扩展
106
123
  token = registryConfig.npmAuthToken;
107
124
  break;
108
125
  }
@@ -110,7 +127,10 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
110
127
  }
111
128
  }
112
129
  }
113
- registryMap.set(normalizedProxiedRegUrl, { normalizedRegistryUrl: normalizedProxiedRegUrl, token });
130
+ registryMap.set(normalizedProxiedRegUrl, {
131
+ normalizedRegistryUrl: normalizedProxiedRegUrl,
132
+ token: token ? token : undefined
133
+ });
114
134
  }
115
135
  const registries = Array.from(registryMap.values());
116
136
  const https = proxyConfig.https;
@@ -382,16 +402,16 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
382
402
  const promisedServer = new Promise((resolve, reject) => {
383
403
  const errHandler = async (err) => {
384
404
  if (err.code === 'EADDRINUSE') {
385
- logger.error(`Port ${port} is in use, please specify a different port or free it.`, err);
386
- await gracefulShutdown();
405
+ reject(new Error(`Port ${port} is in use, please specify a different port or free it.`, { cause: err, }));
406
+ }
407
+ else {
408
+ reject(new Error('Server error', { cause: err, }));
387
409
  }
388
- logger.error('Server error:', err);
389
- reject(err);
390
410
  };
391
411
  const connectionHandler = (socket) => {
392
- logger.info("Server on connection");
393
412
  socket.setTimeout(60000);
394
413
  socket.setKeepAlive(true, 30000);
414
+ logger.info("Server on connection", socket);
395
415
  };
396
416
  server.on('error', errHandler /*this handler will call 'reject'*/);
397
417
  server.on('connection', connectionHandler);
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 解析字符串中的环境变量占位符(格式为 `${ENV_VAR}`),并替换为实际环境变量的值。
3
+ * - 若环境变量不存在,则占位符会被替换为空字符串 `''`。
4
+ * - 若输入为 `null` 或 `undefined`,直接返回原值(不处理)。
5
+ *
6
+ * @param str - 待处理的字符串,可能包含环境变量占位符(如 `${API_URL}`)。支持 `null` 或 `undefined`。
7
+ * @returns 处理后的字符串。若输入为 `null` 或 `undefined`,返回原值;否则返回替换后的新字符串。
8
+ *
9
+ * @example
10
+ * // 环境变量未定义时,替换为 ''
11
+ * resolveEnvValue('${UNDEFINED_VAR}'); // => ''
12
+ *
13
+ * // 混合替换
14
+ * resolveEnvValue('Host: ${HOST}, Port: ${PORT}'); // => 'Host: 127.0.0.1, Port: 3000'(假设环境变量已定义)
15
+ *
16
+ * // 保留 null/undefined
17
+ * resolveEnvValue(null); // => null
18
+ */
19
+ export default function resolveEnvValue(str: string | null | undefined): string | null | undefined;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 解析字符串中的环境变量占位符(格式为 `${ENV_VAR}`),并替换为实际环境变量的值。
3
+ * - 若环境变量不存在,则占位符会被替换为空字符串 `''`。
4
+ * - 若输入为 `null` 或 `undefined`,直接返回原值(不处理)。
5
+ *
6
+ * @param str - 待处理的字符串,可能包含环境变量占位符(如 `${API_URL}`)。支持 `null` 或 `undefined`。
7
+ * @returns 处理后的字符串。若输入为 `null` 或 `undefined`,返回原值;否则返回替换后的新字符串。
8
+ *
9
+ * @example
10
+ * // 环境变量未定义时,替换为 ''
11
+ * resolveEnvValue('${UNDEFINED_VAR}'); // => ''
12
+ *
13
+ * // 混合替换
14
+ * resolveEnvValue('Host: ${HOST}, Port: ${PORT}'); // => 'Host: 127.0.0.1, Port: 3000'(假设环境变量已定义)
15
+ *
16
+ * // 保留 null/undefined
17
+ * resolveEnvValue(null); // => null
18
+ */
19
+ export default function resolveEnvValue(str) {
20
+ // 1. 处理 null 或 undefined 输入:直接返回原值
21
+ if (str == null) {
22
+ return str;
23
+ }
24
+ // 2. 使用正则表达式全局匹配所有 ${...} 占位符
25
+ // - 正则说明: \${(.+?)}
26
+ // - \${ 匹配字面量 `${`
27
+ // - (.+?) 非贪婪匹配任意字符(环境变量名),直到遇到第一个 `}`
28
+ // - /g 标志确保替换全部匹配项(而非仅第一个)
29
+ // - 替换逻辑: 若 process.env[key] 不存在(undefined/null),则返回 ''
30
+ return str.replace(/\${(.+?)}/g, (_, key) => {
31
+ return process.env[key] ?? '';
32
+ });
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "A lightweight npm registry proxy with fallback support",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",