Skip to content

Commit 3ad71b4

Browse files
author
hulutter
committed
feat(privateNpmRegistry): add workspaceId support for cache directory structure
1 parent 89c0d38 commit 3ad71b4

File tree

1 file changed

+65
-32
lines changed
  • server/node-service/src/controllers

1 file changed

+65
-32
lines changed

server/node-service/src/controllers/npm.ts

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import "../common/logger";
22
import fs from "fs/promises";
33
import { spawn } from "child_process";
4-
import { response, Request as ServerRequest, Response as ServerResponse } from "express";
4+
import { Request as ServerRequest, Response as ServerResponse } from "express";
55
import { NpmRegistryService, NpmRegistryConfigEntry, NpmRegistryConfig } from "../services/npmRegistry";
66

77

8+
type RequestConfig = {
9+
workspaceId: string;
10+
npmRegistryConfig: NpmRegistryConfig;
11+
}
12+
13+
type InnerRequestConfig = {
14+
workspaceId?: string;
15+
registry: NpmRegistryConfigEntry;
16+
}
17+
818
type PackagesVersionInfo = {
919
"dist-tags": {
1020
latest: string
@@ -23,24 +33,24 @@ class PackageProcessingQueue {
2333
public static readonly promiseRegistry: {[packageId: string]: Promise<void>} = {};
2434
public static readonly resolveRegistry: {[packageId: string]:() => void} = {};
2535

26-
public static add(packageId: string) {
36+
public static add(packageId: string): void {
2737
PackageProcessingQueue.promiseRegistry[packageId] = new Promise<void>((resolve) => {
2838
PackageProcessingQueue.resolveRegistry[packageId] = resolve;
2939
});
3040
}
3141

32-
public static has(packageId: string) {
42+
public static has(packageId: string): boolean {
3343
return !!PackageProcessingQueue.promiseRegistry[packageId];
3444
}
3545

36-
public static wait(packageId: string) {
46+
public static wait(packageId: string): Promise<void> {
3747
if (!PackageProcessingQueue.has(packageId)) {
3848
return Promise.resolve();
3949
}
4050
return PackageProcessingQueue.promiseRegistry[packageId];
4151
}
4252

43-
public static resolve(packageId: string) {
53+
public static resolve(packageId: string): void {
4454
if (!PackageProcessingQueue.has(packageId)) {
4555
return;
4656
}
@@ -78,10 +88,18 @@ export async function fetchRegistryWithConfig(request: ServerRequest, response:
7888
return response.status(400).send(`Invalid package path: ${path}`);
7989
}
8090

81-
const registryConfig: NpmRegistryConfig = request.body;
82-
const config = NpmRegistryService.getRegistryEntryForPackageWithConfig(pathPackageInfo.packageId, registryConfig);
91+
if (!request.body.workspaceId && !request.body.npmRegistryConfig) {
92+
return response.status(400).send("Missing workspaceId and/or npmRegistryConfig");
93+
}
94+
95+
const {npmRegistryConfig}: RequestConfig = request.body;
96+
97+
const registry = NpmRegistryService.getRegistryEntryForPackageWithConfig(pathPackageInfo.packageId, npmRegistryConfig);
8398

84-
const registryResponse = await fetchFromRegistry(path, config);
99+
const registryResponse = await fetchFromRegistry(path, registry);
100+
if (!registryResponse.ok) {
101+
return response.status(registryResponse.status).send(await registryResponse.text());
102+
}
85103
response.json(await registryResponse.json());
86104
} catch (error) {
87105
logger.error("Error fetching registry", error);
@@ -99,8 +117,11 @@ export async function fetchRegistry(request: ServerRequest, response: ServerResp
99117
return response.status(400).send(`Invalid package path: ${path}`);
100118
}
101119

102-
const config = NpmRegistryService.getInstance().getRegistryEntryForPackage(pathPackageInfo.packageId);
103-
const registryResponse = await fetchFromRegistry(path, config);
120+
const registry = NpmRegistryService.getInstance().getRegistryEntryForPackage(pathPackageInfo.packageId);
121+
const registryResponse = await fetchFromRegistry(path, registry);
122+
if (!registryResponse.ok) {
123+
return response.status(registryResponse.status).send(await registryResponse.text());
124+
}
104125
response.json(await registryResponse.json());
105126
} catch (error) {
106127
logger.error("Error fetching registry", error);
@@ -124,10 +145,15 @@ export async function fetchPackageFileWithConfig(request: ServerRequest, respons
124145
return response.status(400).send(`Invalid package path: ${path}`);
125146
}
126147

127-
const registryConfig: NpmRegistryConfig = request.body;
128-
const config = NpmRegistryService.getRegistryEntryForPackageWithConfig(pathPackageInfo.packageId, registryConfig);
148+
if (!request.body.workspaceId && !request.body.npmRegistryConfig) {
149+
return response.status(400).send("Missing workspaceId and/or npmRegistryConfig");
150+
}
129151

130-
fetchPackageFileInner(request, response, config);
152+
const {workspaceId, npmRegistryConfig}: RequestConfig = request.body;
153+
const registryConfig: NpmRegistryConfig = npmRegistryConfig;
154+
const registry = NpmRegistryService.getRegistryEntryForPackageWithConfig(pathPackageInfo.packageId, registryConfig);
155+
156+
fetchPackageFileInner(request, response, {workspaceId, registry});
131157
}
132158

133159
export async function fetchPackageFile(request: ServerRequest, response: ServerResponse) {
@@ -139,12 +165,14 @@ export async function fetchPackageFile(request: ServerRequest, response: ServerR
139165
return response.status(400).send(`Invalid package path: ${path}`);
140166
}
141167

142-
const config = NpmRegistryService.getInstance().getRegistryEntryForPackage(pathPackageInfo.packageId);
143-
fetchPackageFileInner(request, response, config);
168+
const registry = NpmRegistryService.getInstance().getRegistryEntryForPackage(pathPackageInfo.packageId);
169+
fetchPackageFileInner(request, response, {registry});
144170
}
145171

146-
async function fetchPackageFileInner(request: ServerRequest, response: ServerResponse, config: NpmRegistryConfigEntry) {
172+
async function fetchPackageFileInner(request: ServerRequest, response: ServerResponse, config: InnerRequestConfig) {
147173
try {
174+
const {workspaceId, registry} = config
175+
logger.info(`Fetch file for workspaceId: ${workspaceId}`);
148176
const path = request.path.replace(fetchPackageFileBasePath, "");
149177
const pathPackageInfo = parsePackageInfoFromPath(path);
150178
if (!pathPackageInfo) {
@@ -157,7 +185,7 @@ async function fetchPackageFileInner(request: ServerRequest, response: ServerRes
157185

158186
let packageInfo: PackagesVersionInfo | null = null;
159187
if (version === "latest") {
160-
const packageInfo: PackagesVersionInfo|null = await fetchPackageInfo(packageId, config);
188+
const packageInfo: PackagesVersionInfo|null = await fetchPackageInfo(packageId, registry);
161189
if (packageInfo === null) {
162190
return response.status(404).send("Not found");
163191
}
@@ -170,14 +198,15 @@ async function fetchPackageFileInner(request: ServerRequest, response: ServerRes
170198
await PackageProcessingQueue.wait(packageId);
171199
}
172200

173-
const packageBaseDir = `${CACHE_DIR}/${packageId}/${packageVersion}/package`;
201+
const baseDir = `${CACHE_DIR}/${workspaceId ?? "default"}`;
202+
const packageBaseDir = `${baseDir}/${packageId}/${packageVersion}/package`;
174203
const packageExists = await fileExists(`${packageBaseDir}/package.json`)
175204
if (!packageExists) {
176205
try {
177206
logger.info(`Package does not exist, fetch from registy: ${packageId}@${packageVersion}`);
178207
PackageProcessingQueue.add(packageId);
179208
if (!packageInfo) {
180-
packageInfo = await fetchPackageInfo(packageId, config);
209+
packageInfo = await fetchPackageInfo(packageId, registry);
181210
}
182211

183212
if (!packageInfo || !packageInfo.versions || !packageInfo.versions[packageVersion]) {
@@ -186,9 +215,9 @@ async function fetchPackageFileInner(request: ServerRequest, response: ServerRes
186215

187216
const tarball = packageInfo.versions[packageVersion].dist.tarball;
188217
logger.info(`Fetching tarball: ${tarball}`);
189-
await fetchAndUnpackTarball(tarball, packageId, packageVersion, config);
218+
await fetchAndUnpackTarball(tarball, packageId, packageVersion, registry, baseDir);
190219
} catch (error) {
191-
logger.error("Error fetching package tarball", error);
220+
logger.error(`Error fetching package: ${error} ${(error as {stack: string}).stack}`);
192221
return response.status(500).send("Internal server error");
193222
} finally {
194223
PackageProcessingQueue.resolve(packageId);
@@ -224,6 +253,7 @@ function parsePackageInfoFromPath(path: string): {packageId: string, organizatio
224253
}
225254

226255
let {packageId, organization, name, version, file} = matches.groups;
256+
// also test for alpha and beta versions like 0.0.1-beta1
227257
version = /^\d+\.\d+\.\d+(-[\w\d]+)?/.test(version) ? version : "latest";
228258

229259
return {packageId, organization, name, version, file};
@@ -265,25 +295,28 @@ function fetchPackageInfo(packageName: string, config: NpmRegistryConfigEntry):
265295
});
266296
}
267297

268-
async function fetchAndUnpackTarball(url: string, packageId: string, packageVersion: string, config: NpmRegistryConfigEntry) {
298+
async function fetchAndUnpackTarball(url: string, packageId: string, packageVersion: string, config: NpmRegistryConfigEntry, baseDir: string) {
299+
if (!await fileExists(baseDir)) {
300+
await fs.mkdir(baseDir, { recursive: true });
301+
}
302+
303+
// Fetch tarball
269304
const response: Response = await fetchFromRegistry(url, config);
270305
const arrayBuffer = await response.arrayBuffer();
271306
const buffer = Buffer.from(arrayBuffer);
272-
const path = `${CACHE_DIR}/${url.split("/").pop()}`;
307+
const path = `${baseDir}/${url.split("/").pop()}`;
273308
await fs.writeFile(path, buffer);
274-
await unpackTarball(path, packageId, packageVersion);
275-
await fs.unlink(path);
276-
}
277-
278-
async function unpackTarball(path: string, packageId: string, packageVersion: string) {
279-
const destinationPath = `${CACHE_DIR}/${packageId}/${packageVersion}`;
309+
310+
// Unpack tarball
311+
const destinationPath = `${baseDir}/${packageId}/${packageVersion}`;
280312
await fs.mkdir(destinationPath, { recursive: true });
281313
await new Promise<void> ((resolve, reject) => {
282314
const tar = spawn("tar", ["-xvf", path, "-C", destinationPath]);
283-
tar.on("close", (code) => {
284-
code === 0 ? resolve() : reject();
285-
});
315+
tar.on("close", (code) => code === 0 ? resolve() : reject());
286316
});
317+
318+
// Cleanup
319+
await fs.unlink(path);
287320
}
288321

289322
async function fileExists(filePath: string): Promise<boolean> {

0 commit comments

Comments
 (0)