com.jimuwd.xian.registry-proxy 1.0.28 → 1.0.30

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.
Files changed (3) hide show
  1. package/dist/index.js +24 -21
  2. package/package.json +1 -1
  3. package/src/index.ts +33 -26
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createServer } from 'http';
3
3
  import { createServer as createHttpsServer } from 'https';
4
- import { readFileSync, promises as fsPromises } from 'fs';
4
+ import { promises as fsPromises, readFileSync } from 'fs';
5
5
  import { load } from 'js-yaml';
6
6
  import fetch from 'node-fetch';
7
7
  import { homedir } from 'os';
@@ -78,13 +78,14 @@ function removeRegistryPrefix(tarballUrl, registries) {
78
78
  }
79
79
  throw new Error(`Can't find tarball url ${tarballUrl} does not match given registries ${normalizedRegistries}`);
80
80
  }
81
- async function loadProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
81
+ async function readProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
82
82
  const resolvedPath = resolvePath(proxyConfigPath);
83
83
  try {
84
84
  const content = await readFile(resolvedPath, 'utf8');
85
85
  const config = load(content);
86
86
  if (!config.registries) {
87
- throw new Error('Missing required "registries" field in config');
87
+ console.error('Missing required "registries" field in config');
88
+ process.exit(1);
88
89
  }
89
90
  return config;
90
91
  }
@@ -93,7 +94,7 @@ async function loadProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
93
94
  process.exit(1);
94
95
  }
95
96
  }
96
- async function loadYarnConfig(path) {
97
+ async function readYarnConfig(path) {
97
98
  try {
98
99
  const content = await readFile(resolvePath(path), 'utf8');
99
100
  return load(content);
@@ -103,11 +104,11 @@ async function loadYarnConfig(path) {
103
104
  return {};
104
105
  }
105
106
  }
106
- async function loadRegistries(proxyConfigPath = './.registry-proxy.yml', localYarnConfigPath = './.yarnrc.yml', globalYarnConfigPath = join(homedir(), '.yarnrc.yml')) {
107
+ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYarnConfigPath = './.yarnrc.yml', globalYarnConfigPath = join(homedir(), '.yarnrc.yml')) {
107
108
  const [proxyConfig, localYarnConfig, globalYarnConfig] = await Promise.all([
108
- loadProxyConfig(proxyConfigPath),
109
- loadYarnConfig(localYarnConfigPath),
110
- loadYarnConfig(globalYarnConfigPath)
109
+ readProxyConfig(proxyConfigPath),
110
+ readYarnConfig(localYarnConfigPath),
111
+ readYarnConfig(globalYarnConfigPath)
111
112
  ]);
112
113
  const registryMap = new Map();
113
114
  for (const [url, regConfig] of Object.entries(proxyConfig.registries)) {
@@ -126,15 +127,18 @@ async function loadRegistries(proxyConfigPath = './.registry-proxy.yml', localYa
126
127
  }
127
128
  registryMap.set(normalizedUrl, { url: normalizedUrl, token });
128
129
  }
129
- return Array.from(registryMap.values());
130
+ const registries = Array.from(registryMap.values());
131
+ const https = proxyConfig.https;
132
+ const basePath = removeEndingSlashAndForceStartingSlash(proxyConfig.basePath);
133
+ return { registries, https, basePath };
130
134
  }
131
135
  export async function startProxyServer(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath, port = 0) {
132
- const proxyConfig = await loadProxyConfig(proxyConfigPath);
133
- const registries = await loadRegistries(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
134
- const basePathPrefixedWithSlash = proxyConfig.basePath ? `/${proxyConfig.basePath.replace(/^\/|\/$/g, '')}` : '/';
136
+ const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
137
+ const registries = proxyInfo.registries;
138
+ const basePathPrefixedWithSlash = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
135
139
  console.log('Active registries:', registries.map(r => r.url));
136
140
  console.log('Proxy base path:', basePathPrefixedWithSlash);
137
- console.log('HTTPS:', !!proxyConfig.https);
141
+ console.log('HTTPS:', !!proxyInfo.https);
138
142
  let proxyPort;
139
143
  const requestHandler = async (req, res) => {
140
144
  if (!req.url || !req.headers.host) {
@@ -142,15 +146,14 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
142
146
  res.writeHead(400).end('Invalid Request');
143
147
  return;
144
148
  }
145
- const fullUrl = new URL(req.url, `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host}`);
149
+ const fullUrl = new URL(req.url, `${proxyInfo.https ? 'https' : 'http'}://${req.headers.host}`);
150
+ console.log(`Proxy server received request on ${fullUrl.toString()}`);
146
151
  if (!fullUrl.pathname.startsWith(basePathPrefixedWithSlash)) {
147
152
  console.error(`Path ${fullUrl.pathname} does not match basePath ${basePathPrefixedWithSlash}`);
148
153
  res.writeHead(404).end('Not Found');
149
154
  return;
150
155
  }
151
- const relativePathPrefixedWithSlash = basePathPrefixedWithSlash
152
- ? fullUrl.pathname.slice(basePathPrefixedWithSlash.length)
153
- : fullUrl.pathname;
156
+ const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
154
157
  console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
155
158
  const fetchPromises = registries.map(async ({ url, token }) => {
156
159
  await limiter.acquire();
@@ -185,7 +188,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
185
188
  if (data.versions) {
186
189
  const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
187
190
  console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
188
- const proxyBaseUrlNoSuffixedWithSlash = `${proxyConfig.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
191
+ const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
189
192
  console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
190
193
  for (const version in data.versions) {
191
194
  const dist = data.versions[version]?.dist;
@@ -226,8 +229,8 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
226
229
  }
227
230
  };
228
231
  let server;
229
- if (proxyConfig.https) {
230
- const { key, cert } = proxyConfig.https;
232
+ if (proxyInfo.https) {
233
+ const { key, cert } = proxyInfo.https;
231
234
  const keyPath = resolvePath(key);
232
235
  const certPath = resolvePath(cert);
233
236
  try {
@@ -261,7 +264,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
261
264
  proxyPort = address.port;
262
265
  const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
263
266
  writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
264
- console.log(`Proxy server running on ${proxyConfig.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash}`);
267
+ console.log(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash}`);
265
268
  resolve(server);
266
269
  });
267
270
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "type": "module",
5
5
  "description": "A lightweight npm registry proxy with fallback support",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import {createServer, Server as HttpServer, IncomingMessage, ServerResponse, OutgoingHttpHeaders} from 'http';
2
+ import {createServer, IncomingMessage, OutgoingHttpHeaders, Server as HttpServer, ServerResponse} from 'http';
3
3
  import {createServer as createHttpsServer, Server as HttpsServer} from 'https';
4
- import {readFileSync, promises as fsPromises} from 'fs';
4
+ import {promises as fsPromises, readFileSync} from 'fs';
5
5
  import {AddressInfo} from 'net';
6
6
  import {load} from 'js-yaml';
7
7
  import fetch, {Response} from 'node-fetch';
@@ -35,6 +35,12 @@ interface RegistryInfo {
35
35
  token?: string;
36
36
  }
37
37
 
38
+ interface ProxyInfo {
39
+ registries: RegistryInfo[];
40
+ https?: HttpsConfig;
41
+ basePath?: string;
42
+ }
43
+
38
44
  interface PackageVersion {
39
45
  dist?: { tarball?: string };
40
46
  }
@@ -74,7 +80,7 @@ class ConcurrencyLimiter {
74
80
 
75
81
  const limiter = new ConcurrencyLimiter(3);
76
82
 
77
- function removeEndingSlashAndForceStartingSlash(str: string): string {
83
+ function removeEndingSlashAndForceStartingSlash(str: string | undefined | null): string {
78
84
  if (!str) return '/';
79
85
  let trimmed = str.trim();
80
86
  if (trimmed === '/') return '/';
@@ -115,13 +121,14 @@ function removeRegistryPrefix(tarballUrl: string, registries: RegistryInfo[]): s
115
121
  throw new Error(`Can't find tarball url ${tarballUrl} does not match given registries ${normalizedRegistries}`)
116
122
  }
117
123
 
118
- async function loadProxyConfig(proxyConfigPath = './.registry-proxy.yml'): Promise<ProxyConfig> {
124
+ async function readProxyConfig(proxyConfigPath = './.registry-proxy.yml'): Promise<ProxyConfig> {
119
125
  const resolvedPath = resolvePath(proxyConfigPath);
120
126
  try {
121
127
  const content = await readFile(resolvedPath, 'utf8');
122
128
  const config = load(content) as ProxyConfig;
123
129
  if (!config.registries) {
124
- throw new Error('Missing required "registries" field in config');
130
+ console.error('Missing required "registries" field in config');
131
+ process.exit(1);
125
132
  }
126
133
  return config;
127
134
  } catch (e) {
@@ -130,7 +137,7 @@ async function loadProxyConfig(proxyConfigPath = './.registry-proxy.yml'): Promi
130
137
  }
131
138
  }
132
139
 
133
- async function loadYarnConfig(path: string): Promise<YarnConfig> {
140
+ async function readYarnConfig(path: string): Promise<YarnConfig> {
134
141
  try {
135
142
  const content = await readFile(resolvePath(path), 'utf8');
136
143
  return load(content) as YarnConfig;
@@ -140,22 +147,20 @@ async function loadYarnConfig(path: string): Promise<YarnConfig> {
140
147
  }
141
148
  }
142
149
 
143
- async function loadRegistries(
150
+ async function loadProxyInfo(
144
151
  proxyConfigPath = './.registry-proxy.yml',
145
152
  localYarnConfigPath = './.yarnrc.yml',
146
153
  globalYarnConfigPath = join(homedir(), '.yarnrc.yml')
147
- ): Promise<RegistryInfo[]> {
154
+ ): Promise<ProxyInfo> {
148
155
  const [proxyConfig, localYarnConfig, globalYarnConfig] = await Promise.all([
149
- loadProxyConfig(proxyConfigPath),
150
- loadYarnConfig(localYarnConfigPath),
151
- loadYarnConfig(globalYarnConfigPath)
156
+ readProxyConfig(proxyConfigPath),
157
+ readYarnConfig(localYarnConfigPath),
158
+ readYarnConfig(globalYarnConfigPath)
152
159
  ]);
153
-
154
160
  const registryMap = new Map<string, RegistryInfo>();
155
161
  for (const [url, regConfig] of Object.entries(proxyConfig.registries)) {
156
162
  const normalizedUrl = normalizeUrl(url);
157
163
  let token = regConfig?.npmAuthToken;
158
-
159
164
  if (!token) {
160
165
  const yarnConfigs = [localYarnConfig, globalYarnConfig];
161
166
  for (const config of yarnConfigs) {
@@ -169,7 +174,10 @@ async function loadRegistries(
169
174
  }
170
175
  registryMap.set(normalizedUrl, {url: normalizedUrl, token});
171
176
  }
172
- return Array.from(registryMap.values());
177
+ const registries = Array.from(registryMap.values());
178
+ const https = proxyConfig.https;
179
+ const basePath = removeEndingSlashAndForceStartingSlash(proxyConfig.basePath);
180
+ return {registries, https, basePath};
173
181
  }
174
182
 
175
183
  export async function startProxyServer(
@@ -178,13 +186,13 @@ export async function startProxyServer(
178
186
  globalYarnConfigPath?: string,
179
187
  port: number = 0
180
188
  ): Promise<HttpServer | HttpsServer> {
181
- const proxyConfig = await loadProxyConfig(proxyConfigPath);
182
- const registries = await loadRegistries(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
183
- const basePathPrefixedWithSlash: string = proxyConfig.basePath ? `/${proxyConfig.basePath.replace(/^\/|\/$/g, '')}` : '/';
189
+ const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
190
+ const registries = proxyInfo.registries;
191
+ const basePathPrefixedWithSlash: string = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
184
192
 
185
193
  console.log('Active registries:', registries.map(r => r.url));
186
194
  console.log('Proxy base path:', basePathPrefixedWithSlash);
187
- console.log('HTTPS:', !!proxyConfig.https);
195
+ console.log('HTTPS:', !!proxyInfo.https);
188
196
 
189
197
  let proxyPort: number;
190
198
 
@@ -195,16 +203,15 @@ export async function startProxyServer(
195
203
  return;
196
204
  }
197
205
 
198
- const fullUrl = new URL(req.url, `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host}`);
206
+ const fullUrl = new URL(req.url, `${proxyInfo.https ? 'https' : 'http'}://${req.headers.host}`);
207
+ console.log(`Proxy server received request on ${fullUrl.toString()}`)
199
208
  if (!fullUrl.pathname.startsWith(basePathPrefixedWithSlash)) {
200
209
  console.error(`Path ${fullUrl.pathname} does not match basePath ${basePathPrefixedWithSlash}`);
201
210
  res.writeHead(404).end('Not Found');
202
211
  return;
203
212
  }
204
213
 
205
- const relativePathPrefixedWithSlash = basePathPrefixedWithSlash
206
- ? fullUrl.pathname.slice(basePathPrefixedWithSlash.length)
207
- : fullUrl.pathname;
214
+ const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
208
215
  console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
209
216
 
210
217
  const fetchPromises = registries.map(async ({url, token}) => {
@@ -240,7 +247,7 @@ export async function startProxyServer(
240
247
  if (data.versions) {
241
248
  const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
242
249
  console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
243
- const proxyBaseUrlNoSuffixedWithSlash = `${proxyConfig.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
250
+ const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
244
251
  console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
245
252
  for (const version in data.versions) {
246
253
  const dist = data.versions[version]?.dist;
@@ -278,8 +285,8 @@ export async function startProxyServer(
278
285
  };
279
286
 
280
287
  let server: HttpServer | HttpsServer;
281
- if (proxyConfig.https) {
282
- const {key, cert} = proxyConfig.https;
288
+ if (proxyInfo.https) {
289
+ const {key, cert} = proxyInfo.https;
283
290
  const keyPath = resolvePath(key);
284
291
  const certPath = resolvePath(cert);
285
292
  try {
@@ -312,7 +319,7 @@ export async function startProxyServer(
312
319
  proxyPort = address.port;
313
320
  const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
314
321
  writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
315
- console.log(`Proxy server running on ${proxyConfig.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash}`);
322
+ console.log(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash}`);
316
323
  resolve(server);
317
324
  });
318
325
  });