mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
feat: allow python custom registries configuration through config file
This commit is contained in:
parent
39e2001d97
commit
c53a7347e2
5 changed files with 325 additions and 283 deletions
12
README.md
12
README.md
|
|
@ -188,9 +188,13 @@ You can set the minimum package age through multiple sources (in order of priori
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom NPM Registries
|
## Custom Registries
|
||||||
|
|
||||||
Configure Safe Chain to scan packages from custom or private npm registries.
|
Configure Safe Chain to scan packages from custom or private registries.
|
||||||
|
|
||||||
|
Supported ecosystems:
|
||||||
|
- Node.js
|
||||||
|
- Python
|
||||||
|
|
||||||
### Configuration Options
|
### Configuration Options
|
||||||
|
|
||||||
|
|
@ -200,6 +204,7 @@ You can set custom registries through environment variable or config file. Both
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
export SAFE_CHAIN_NPM_CUSTOM_REGISTRIES="npm.company.com,registry.internal.net"
|
export SAFE_CHAIN_NPM_CUSTOM_REGISTRIES="npm.company.com,registry.internal.net"
|
||||||
|
export SAFE_CHAIN_PIP_CUSTOM_REGISTRIES="pip.company.com,registry.internal.net"
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Config File** (`~/.aikido/config.json`):
|
2. **Config File** (`~/.aikido/config.json`):
|
||||||
|
|
@ -208,6 +213,9 @@ You can set custom registries through environment variable or config file. Both
|
||||||
{
|
{
|
||||||
"npm": {
|
"npm": {
|
||||||
"customRegistries": ["npm.company.com", "registry.internal.net"]
|
"customRegistries": ["npm.company.com", "registry.internal.net"]
|
||||||
|
},
|
||||||
|
"pip": {
|
||||||
|
"customRegistries": ["pip.company.com", "registry.internal.net"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { getEcoSystem } from "./settings.js";
|
||||||
* @property {unknown | Number} scanTimeout
|
* @property {unknown | Number} scanTimeout
|
||||||
* @property {unknown | Number} minimumPackageAgeHours
|
* @property {unknown | Number} minimumPackageAgeHours
|
||||||
* @property {unknown | SafeChainRegistryConfiguration} npm
|
* @property {unknown | SafeChainRegistryConfiguration} npm
|
||||||
|
* @property {unknown | SafeChainRegistryConfiguration} pip
|
||||||
*
|
*
|
||||||
* @typedef {Object} SafeChainRegistryConfiguration
|
* @typedef {Object} SafeChainRegistryConfiguration
|
||||||
* We cannot trust the input and should add the necessary validations.
|
* We cannot trust the input and should add the necessary validations.
|
||||||
|
|
@ -104,6 +105,28 @@ export function getNpmCustomRegistries() {
|
||||||
return customRegistries.filter((item) => typeof item === "string");
|
return customRegistries.filter((item) => typeof item === "string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the custom npm registries from the config file (format parsing only, no validation)
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
export function getPipCustomRegistries() {
|
||||||
|
const config = readConfigFile();
|
||||||
|
|
||||||
|
if (!config || !config.pip) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeScript needs help understanding that config.pip exists and has customRegistries
|
||||||
|
const pipConfig = /** @type {SafeChainRegistryConfiguration} */ (config.pip);
|
||||||
|
const customRegistries = pipConfig.customRegistries;
|
||||||
|
|
||||||
|
if (!Array.isArray(customRegistries)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return customRegistries.filter((item) => typeof item === "string");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../api/aikido.js").MalwarePackage[]} data
|
* @param {import("../api/aikido.js").MalwarePackage[]} data
|
||||||
* @param {string | number} version
|
* @param {string | number} version
|
||||||
|
|
@ -169,6 +192,9 @@ function readConfigFile() {
|
||||||
npm: {
|
npm: {
|
||||||
customRegistries: undefined,
|
customRegistries: undefined,
|
||||||
},
|
},
|
||||||
|
pip: {
|
||||||
|
customRegistries: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const configFilePath = getConfigFilePath();
|
const configFilePath = getConfigFilePath();
|
||||||
|
|
|
||||||
|
|
@ -232,91 +232,95 @@ describe("getMinimumPackageAgeHours", async () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getNpmCustomRegistries", async () => {
|
for (const packageManager of ["npm", "pip"]) {
|
||||||
const { getNpmCustomRegistries } = await import("./configFile.js");
|
const fnName = `get${packageManager.charAt(0).toUpperCase()}${packageManager.slice(1)}CustomRegistries`;
|
||||||
|
|
||||||
afterEach(() => {
|
describe(fnName, async () => {
|
||||||
configFileContent = undefined;
|
const fn = (await import("./configFile.js"))[fnName];
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array when config file doesn't exist", () => {
|
afterEach(() => {
|
||||||
configFileContent = undefined;
|
configFileContent = undefined;
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array when npm config is not set", () => {
|
|
||||||
configFileContent = JSON.stringify({ scanTimeout: 5000 });
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array when customRegistries is not an array", () => {
|
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: { customRegistries: "not-an-array" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should return empty array when config file doesn't exist", () => {
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
const registries = fn();
|
||||||
});
|
|
||||||
|
|
||||||
it("should return array of custom registries when set", () => {
|
assert.deepStrictEqual(registries, []);
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: {
|
|
||||||
customRegistries: ["npm.company.com", "registry.internal.net"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it(`should return empty array when ${packageManager} config is not set`, () => {
|
||||||
|
configFileContent = JSON.stringify({ scanTimeout: 5000 });
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"npm.company.com",
|
|
||||||
"registry.internal.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should filter out non-string values", () => {
|
assert.deepStrictEqual(registries, []);
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: {
|
|
||||||
customRegistries: [
|
|
||||||
"npm.company.com",
|
|
||||||
123,
|
|
||||||
null,
|
|
||||||
"registry.internal.net",
|
|
||||||
undefined,
|
|
||||||
{},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should return empty array when customRegistries is not an array", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: { customRegistries: "not-an-array" },
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"npm.company.com",
|
|
||||||
"registry.internal.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array for empty customRegistries array", () => {
|
assert.deepStrictEqual(registries, []);
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: { customRegistries: [] },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should return array of custom registries when set", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [`${packageManager}.company.com`, "registry.internal.net"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
`${packageManager}.company.com`,
|
||||||
|
"registry.internal.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out non-string values", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [
|
||||||
|
`${packageManager}.company.com`,
|
||||||
|
123,
|
||||||
|
null,
|
||||||
|
"registry.internal.net",
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
`${packageManager}.company.com`,
|
||||||
|
"registry.internal.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty array for empty customRegistries array", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: { customRegistries: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle malformed JSON and return empty array", () => {
|
||||||
|
configFileContent = "{ invalid json";
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, []);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
it("should handle malformed JSON and return empty array", () => {
|
|
||||||
configFileContent = "{ invalid json";
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -152,11 +152,10 @@ export function getPipCustomRegistries() {
|
||||||
const envRegistries = parseRegistriesFromEnv(
|
const envRegistries = parseRegistriesFromEnv(
|
||||||
environmentVariables.getPipCustomRegistries()
|
environmentVariables.getPipCustomRegistries()
|
||||||
);
|
);
|
||||||
// const configRegistries = configFile.getPipCustomRegistries();
|
const configRegistries = configFile.getPipCustomRegistries();
|
||||||
|
|
||||||
// Merge both sources and remove duplicates
|
// Merge both sources and remove duplicates
|
||||||
// const allRegistries = [...envRegistries, ...configRegistries];
|
const allRegistries = [...envRegistries, ...configRegistries];
|
||||||
const allRegistries = [...envRegistries];
|
|
||||||
const uniqueRegistries = [...new Set(allRegistries)];
|
const uniqueRegistries = [...new Set(allRegistries)];
|
||||||
|
|
||||||
// Normalize each registry (remove protocol if any)
|
// Normalize each registry (remove protocol if any)
|
||||||
|
|
|
||||||
|
|
@ -11,239 +11,244 @@ mock.module("fs", {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getNpmCustomRegistries", async () => {
|
for (const packageManager of ["npm", "pip"]) {
|
||||||
let originalEnv;
|
const fnName = `get${packageManager.charAt(0).toUpperCase()}${packageManager.slice(1)}CustomRegistries`;
|
||||||
const { getNpmCustomRegistries } = await import("./settings.js");
|
const envVarName = `SAFE_CHAIN_${packageManager.toUpperCase()}_CUSTOM_REGISTRIES`;
|
||||||
|
|
||||||
beforeEach(() => {
|
describe(fnName, async () => {
|
||||||
originalEnv = process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
let originalEnv;
|
||||||
});
|
const fn = (await import("./settings.js"))[fnName];
|
||||||
|
|
||||||
afterEach(() => {
|
beforeEach(() => {
|
||||||
if (originalEnv !== undefined) {
|
originalEnv = process.env[envVarName];
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES = originalEnv;
|
|
||||||
} else {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
}
|
|
||||||
configFileContent = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array when no registries configured", () => {
|
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return registries without protocol", () => {
|
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: {
|
|
||||||
customRegistries: ["npm.company.com", "registry.internal.net"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
afterEach(() => {
|
||||||
|
if (originalEnv !== undefined) {
|
||||||
assert.deepStrictEqual(registries, [
|
process.env[envVarName] = originalEnv;
|
||||||
"npm.company.com",
|
} else {
|
||||||
"registry.internal.net",
|
delete process.env[envVarName];
|
||||||
]);
|
}
|
||||||
});
|
configFileContent = undefined;
|
||||||
|
|
||||||
it("should strip https:// protocol from registries", () => {
|
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: {
|
|
||||||
customRegistries: [
|
|
||||||
"https://npm.company.com",
|
|
||||||
"https://registry.internal.net",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should return empty array when no registries configured", () => {
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"npm.company.com",
|
|
||||||
"registry.internal.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should strip http:// protocol from registries", () => {
|
assert.deepStrictEqual(registries, []);
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: {
|
|
||||||
customRegistries: [
|
|
||||||
"http://npm.company.com",
|
|
||||||
"http://registry.internal.net",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should return registries without protocol", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [`${packageManager}.company.com`, "registry.internal.net"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"npm.company.com",
|
|
||||||
"registry.internal.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle mixed protocols and no protocol", () => {
|
assert.deepStrictEqual(registries, [
|
||||||
configFileContent = JSON.stringify({
|
`${packageManager}.company.com`,
|
||||||
npm: {
|
"registry.internal.net",
|
||||||
customRegistries: [
|
]);
|
||||||
"https://npm.company.com",
|
|
||||||
"registry.internal.net",
|
|
||||||
"http://private.registry.io",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should strip https:// protocol from registries", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [
|
||||||
|
`https://${packageManager}.company.com`,
|
||||||
|
"https://registry.internal.net",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"npm.company.com",
|
|
||||||
"registry.internal.net",
|
|
||||||
"private.registry.io",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should preserve registry path after stripping protocol", () => {
|
assert.deepStrictEqual(registries, [
|
||||||
configFileContent = JSON.stringify({
|
`${packageManager}.company.com`,
|
||||||
npm: {
|
"registry.internal.net",
|
||||||
customRegistries: [
|
]);
|
||||||
"https://npm.company.com/custom/path",
|
|
||||||
"registry.internal.net/npm",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should strip http:// protocol from registries", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [
|
||||||
|
`http://${packageManager}.company.com`,
|
||||||
|
"http://registry.internal.net",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"npm.company.com/custom/path",
|
|
||||||
"registry.internal.net/npm",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should parse comma-separated registries from environment variable", () => {
|
assert.deepStrictEqual(registries, [
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
`${packageManager}.company.com`,
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES =
|
"registry.internal.net",
|
||||||
"env1.registry.com,env2.registry.net";
|
]);
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
|
||||||
"env1.registry.com",
|
|
||||||
"env2.registry.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should trim whitespace from environment variable registries", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES =
|
|
||||||
" env1.registry.com , env2.registry.net ";
|
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
|
||||||
"env1.registry.com",
|
|
||||||
"env2.registry.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should merge environment variable and config file registries", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES = "env1.registry.com";
|
|
||||||
configFileContent = JSON.stringify({
|
|
||||||
npm: {
|
|
||||||
customRegistries: ["config1.registry.net"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should handle mixed protocols and no protocol", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [
|
||||||
|
`https://${packageManager}.company.com`,
|
||||||
|
"registry.internal.net",
|
||||||
|
"http://private.registry.io",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"env1.registry.com",
|
|
||||||
"config1.registry.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove duplicate registries when merging env and config", () => {
|
assert.deepStrictEqual(registries, [
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
`${packageManager}.company.com`,
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES =
|
"registry.internal.net",
|
||||||
"npm.company.com,env.registry.com";
|
"private.registry.io",
|
||||||
configFileContent = JSON.stringify({
|
]);
|
||||||
npm: {
|
|
||||||
customRegistries: ["npm.company.com", "config.registry.net"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
it("should preserve registry path after stripping protocol", () => {
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [
|
||||||
|
`https://${packageManager}.company.com/custom/path`,
|
||||||
|
`registry.internal.net/${packageManager}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
const registries = fn();
|
||||||
"npm.company.com",
|
|
||||||
"env.registry.com",
|
assert.deepStrictEqual(registries, [
|
||||||
"config.registry.net",
|
`${packageManager}.company.com/custom/path`,
|
||||||
]);
|
`registry.internal.net/${packageManager}`,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse comma-separated registries from environment variable", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] =
|
||||||
|
"env1.registry.com,env2.registry.net";
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
"env1.registry.com",
|
||||||
|
"env2.registry.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trim whitespace from environment variable registries", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] =
|
||||||
|
" env1.registry.com , env2.registry.net ";
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
"env1.registry.com",
|
||||||
|
"env2.registry.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should merge environment variable and config file registries", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] = "env1.registry.com";
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: ["config1.registry.net"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
"env1.registry.com",
|
||||||
|
"config1.registry.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove duplicate registries when merging env and config", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] =
|
||||||
|
`${packageManager}.company.com,env.registry.com`;
|
||||||
|
configFileContent = JSON.stringify({
|
||||||
|
[packageManager]: {
|
||||||
|
customRegistries: [`${packageManager}.company.com`, "config.registry.net"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
`${packageManager}.company.com`,
|
||||||
|
"env.registry.com",
|
||||||
|
"config.registry.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should normalize protocols from environment variable registries", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] =
|
||||||
|
"https://env1.registry.com,http://env2.registry.net";
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
"env1.registry.com",
|
||||||
|
"env2.registry.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty strings in comma-separated list", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] =
|
||||||
|
"env1.registry.com,,env2.registry.net,";
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, [
|
||||||
|
"env1.registry.com",
|
||||||
|
"env2.registry.net",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle single registry in environment variable", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] = "single.registry.com";
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, ["single.registry.com"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty array for empty environment variable", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] = "";
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty array for whitespace-only environment variable", () => {
|
||||||
|
delete process.env[envVarName];
|
||||||
|
process.env[envVarName] = " , , ";
|
||||||
|
configFileContent = undefined;
|
||||||
|
|
||||||
|
const registries = fn();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(registries, []);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
it("should normalize protocols from environment variable registries", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES =
|
|
||||||
"https://env1.registry.com,http://env2.registry.net";
|
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
|
||||||
"env1.registry.com",
|
|
||||||
"env2.registry.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle empty strings in comma-separated list", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES =
|
|
||||||
"env1.registry.com,,env2.registry.net,";
|
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, [
|
|
||||||
"env1.registry.com",
|
|
||||||
"env2.registry.net",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle single registry in environment variable", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES = "single.registry.com";
|
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, ["single.registry.com"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array for empty environment variable", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES = "";
|
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array for whitespace-only environment variable", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
||||||
process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES = " , , ";
|
|
||||||
configFileContent = undefined;
|
|
||||||
|
|
||||||
const registries = getNpmCustomRegistries();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(registries, []);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue