Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): use user configured npm registry #4937

Merged
merged 8 commits into from
Aug 22, 2024
1 change: 1 addition & 0 deletions packages/core/src/di/core-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const checkerFactory = 'checkerFactory';
export const checkerConcurrencyTokens = 'checkerConcurrencyTokens';
export const disableTypeChecksHelper = 'disableTypeChecksHelper';
export const execa = 'execa';
export const execaSync = 'execaSync';
export const dryRunResult = 'dryRunResult';
export const mutants = 'mutants';
export const mutantTestPlanner = 'mutantTestPlanner';
Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/initializer/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { createInjector } from 'typed-inject';
import { RestClient } from 'typed-rest-client';
import { execaCommand } from 'execa';
import { execaCommand, execaCommandSync } from 'execa';
import { resolveFromCwd } from '@stryker-mutator/util';
import { LogLevel } from '@stryker-mutator/api/core';

import { coreTokens, provideLogger } from '../di/index.js';

import { LogConfigurator } from '../logging/log-configurator.js';
import { LogConfigurator } from '../logging/index.js';

import * as initializerTokens from './initializer-tokens.js';
import { NpmClient } from './npm-client.js';
Expand All @@ -15,20 +14,21 @@ import { StrykerInitializer } from './stryker-initializer.js';
import { StrykerInquirer } from './stryker-inquirer.js';
import { createInitializers } from './custom-initializers/index.js';
import { GitignoreWriter } from './gitignore-writer.js';

const NPM_REGISTRY = 'https://registry.npmjs.com';
import { createNpmRegistryClient, getRegistry } from './npm-registry.js';

export function initializerFactory(): StrykerInitializer {
LogConfigurator.configureMainProcess(LogLevel.Information);
return provideLogger(createInjector())
.provideValue(initializerTokens.out, console.log)
.provideValue(initializerTokens.restClientNpm, new RestClient('npm', NPM_REGISTRY))
nicojs marked this conversation as resolved.
Show resolved Hide resolved
.provideValue(coreTokens.execa, execaCommand)
.provideValue(coreTokens.execaSync, execaCommandSync)
.provideValue(coreTokens.resolveFromCwd, resolveFromCwd)
.provideFactory(initializerTokens.npmRegistry, getRegistry)
.provideFactory(initializerTokens.restClientNpm, createNpmRegistryClient)
.provideClass(initializerTokens.npmClient, NpmClient)
.provideClass(initializerTokens.configWriter, StrykerConfigWriter)
.provideClass(initializerTokens.gitignoreWriter, GitignoreWriter)
.provideClass(initializerTokens.inquirer, StrykerInquirer)
.provideValue(coreTokens.execa, execaCommand)
.provideValue(coreTokens.resolveFromCwd, resolveFromCwd)
.provideFactory(initializerTokens.customInitializers, createInitializers)
.injectClass(StrykerInitializer);
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/initializer/initializer-tokens.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const restClientNpm = 'restClientNpm';
export const npmClient = 'npmClient';
export const npmRegistry = 'npmRegistry';
export const customInitializers = 'strykerPresets';
export const configWriter = 'configWriter';
export const gitignoreWriter = 'gitignoreWriter';
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/initializer/npm-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ const handleResult =
};

export class NpmClient {
public static inject = tokens(commonTokens.logger, initializerTokens.restClientNpm);
public static inject = tokens(commonTokens.logger, initializerTokens.restClientNpm, initializerTokens.npmRegistry);
constructor(
private readonly log: Logger,
private readonly innerNpmClient: RestClient,
private readonly npmRegistry: string,
) {}

public getTestRunnerOptions(): Promise<PromptOption[]> {
Expand Down Expand Up @@ -65,7 +66,7 @@ export class NpmClient {
const response = await this.innerNpmClient.get<NpmSearchResult>(path);
return handleResult(path)(response);
} catch (err) {
this.log.error(`Unable to reach 'https://registry.npmjs.com' (for query ${path}). Please check your internet connection.`, errorToString(err));
this.log.error(`Unable to reach '${this.npmRegistry}' (for query ${path}). Please check your internet connection.`, errorToString(err));
const result: NpmSearchResult = {
objects: [],
total: 0,
Expand Down
42 changes: 42 additions & 0 deletions packages/core/src/initializer/npm-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { execaCommandSync } from 'execa';
import { RestClient } from 'typed-rest-client';
import * as initializerTokens from './initializer-tokens.js';
import { coreTokens } from '../di/index.js';
import { errorToString } from '@stryker-mutator/util';
import { Logger } from '@stryker-mutator/api/logging';
import { commonTokens } from '@stryker-mutator/api/plugin';

const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.com';

function getRegistry(logger: Logger, execaSync: typeof execaCommandSync): string {
if (process.env.npm_config_registry) {
return process.env.npm_config_registry;
} else if (process.env.npm_command) {
// if running inside npm and not having the registry than it's the default one
return DEFAULT_NPM_REGISTRY;
} else {
// Using global as when trying to get the registry inside npm workspace it would fail
try {
const registry = execaSync('npm config get --global registry', {
stdout: 'pipe',
timeout: 20000,
});

return registry.stdout.trim();
} catch (e) {
logger.warn('Could not run `npm config get --global registry` falling back to default npm registry.', errorToString(e));

return DEFAULT_NPM_REGISTRY;
}
}
}

getRegistry.inject = [commonTokens.logger, coreTokens.execaSync] as const;

function createNpmRegistryClient(npmRegistry: string): RestClient {
return new RestClient('npm', npmRegistry);
}

createNpmRegistryClient.inject = [initializerTokens.npmRegistry] as const;

export { createNpmRegistryClient, getRegistry };
87 changes: 87 additions & 0 deletions packages/core/test/unit/initializer/npm-registry.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { getRegistry } from '../../../src/initializer/npm-registry.js';
import { expect } from 'chai';
import { testInjector } from '@stryker-mutator/test-helpers';
import sinon from 'sinon';
import { execaCommandSync } from 'execa';

const DEFAULT_REGISTRY = 'https://registry.npmjs.com';

describe('npm registry', () => {
describe('get registry', () => {
let oldNpmConfigRegistry: string | undefined;
let oldNpmCommand: string | undefined;

before(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be beforeEach instead of before (or change afterEach to be after)

oldNpmConfigRegistry = process.env.npm_config_registry;
oldNpmCommand = process.env.npm_command;
});

afterEach(() => {
process.env.npm_config_registry = oldNpmConfigRegistry;
process.env.npm_command = oldNpmCommand;
});

it('should return default repository when run with npm command with no custom repository', () => {
const execaCommandSyncMock = sinon.spy();

process.env.npm_config_registry = '';
process.env.npm_command = 'value';

const registry = getRegistry(testInjector.logger, execaCommandSyncMock);

expect(registry).to.equal(DEFAULT_REGISTRY);
sinon.assert.callCount(execaCommandSyncMock, 0);
});

it('should return default repository when run with npm command with custom repository', () => {
const execaCommandSyncMock = sinon.spy();
process.env.npm_config_registry = 'foo';
process.env.npm_command = 'value';

const registry = getRegistry(testInjector.logger, execaCommandSyncMock);

expect(registry).to.equal('foo');
sinon.assert.callCount(execaCommandSyncMock, 0);
});

it('should return globally configured npm registry when run with node command', () => {
const expectedRegistry = 'http://my.custom.npm.registry.stryker';
const execaCommandSyncMock = sinon.spy((_command, _options) => ({ stdout: expectedRegistry }));
process.env.npm_config_registry = '';
process.env.npm_command = '';

const registry = getRegistry(testInjector.logger, execaCommandSyncMock as unknown as typeof execaCommandSync);

sinon.assert.callCount(execaCommandSyncMock, 1);
sinon.assert.calledWith(execaCommandSyncMock, 'npm config get --global registry', {
stdout: 'pipe',
timeout: 20000,
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be simplified to calledOnceWithExactly


expect(registry).to.equal(expectedRegistry);
});

it('should return default repository and warn the user if run with node command and execa command failed', () => {
const execaCommandSyncMock = sinon.spy((_command, _options) => {
throw new Error();
});

process.env.npm_config_registry = '';
process.env.npm_command = '';

const registry = getRegistry(testInjector.logger, execaCommandSyncMock as unknown as typeof execaCommandSync);

sinon.assert.callCount(execaCommandSyncMock, 1);
sinon.assert.calledWith(execaCommandSyncMock, 'npm config get --global registry', {
stdout: 'pipe',
timeout: 20000,
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here


expect(registry).to.equal(DEFAULT_REGISTRY);
sinon.assert.calledOnceWithMatch(
testInjector.logger.warn,
'Could not run `npm config get --global registry` falling back to default npm registry.',
);
});
});
nicojs marked this conversation as resolved.
Show resolved Hide resolved
});
10 changes: 6 additions & 4 deletions packages/core/test/unit/initializer/stryker-initializer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { expect } from 'chai';
import sinon from 'sinon';
import typedRestClient, { type RestClient } from 'typed-rest-client/RestClient.js';

import { fileUtils } from '../../../src/utils/file-utils.js';
import { fileUtils } from '../../../src/utils/index.js';
import { initializerTokens } from '../../../src/initializer/index.js';
import { NpmClient, NpmSearchResult } from '../../../src/initializer/npm-client.js';
import { StrykerConfigWriter } from '../../../src/initializer/stryker-config-writer.js';
import { StrykerInitializer } from '../../../src/initializer/stryker-initializer.js';
import { StrykerInquirer } from '../../../src/initializer/stryker-inquirer.js';
import { Mock } from '../../helpers/producers.js';
import { GitignoreWriter } from '../../../src/initializer/gitignore-writer.js';
import { SUPPORTED_CONFIG_FILE_NAMES } from '../../../src/config/config-file-formats.js';
import { SUPPORTED_CONFIG_FILE_NAMES } from '../../../src/config/index.js';
import { CustomInitializer, CustomInitializerConfiguration } from '../../../src/initializer/custom-initializers/custom-initializer.js';
import { PackageInfo } from '../../../src/initializer/package-info.js';
import { inquire } from '../../../src/initializer/inquire.js';
Expand All @@ -35,6 +35,7 @@ describe(StrykerInitializer.name, () => {
let out: sinon.SinonStub;
let customInitializers: CustomInitializer[];
let customInitializerMock: Mock<CustomInitializer>;
const defaultNpmRegistry = 'https://registry.npmjs.com';

beforeEach(() => {
out = sinon.stub();
Expand All @@ -55,6 +56,7 @@ describe(StrykerInitializer.name, () => {
syncBuiltinESMExports();
sut = testInjector.injector
.provideValue(initializerTokens.out, out as unknown as typeof console.log)
.provideValue(initializerTokens.npmRegistry, defaultNpmRegistry)
.provideValue(initializerTokens.restClientNpm, npmRestClient)
.provideClass(initializerTokens.inquirer, StrykerInquirer)
.provideClass(initializerTokens.npmClient, NpmClient)
Expand Down Expand Up @@ -428,7 +430,7 @@ describe(StrykerInitializer.name, () => {
await sut.initialize();

expect(testInjector.logger.error).calledWith(
"Unable to reach 'https://registry.npmjs.com' (for query /-/v1/search?text=keywords:%40stryker-mutator%2Ftest-runner-plugin). Please check your internet connection.",
`Unable to reach '${defaultNpmRegistry}' (for query /-/v1/search?text=keywords:%40stryker-mutator%2Ftest-runner-plugin). Please check your internet connection.`,
);
expect(fs.promises.writeFile).calledWith('stryker.config.json', sinon.match('"testRunner": "command"'));
});
Expand All @@ -447,7 +449,7 @@ describe(StrykerInitializer.name, () => {
await sut.initialize();

expect(testInjector.logger.error).calledWith(
"Unable to reach 'https://registry.npmjs.com' (for query /-/v1/search?text=keywords:%40stryker-mutator%2Freporter-plugin). Please check your internet connection.",
`Unable to reach '${defaultNpmRegistry}' (for query /-/v1/search?text=keywords:%40stryker-mutator%2Freporter-plugin). Please check your internet connection.`,
);
expect(fs.promises.writeFile).called;
});
Expand Down