Skip to content

Commit

Permalink
feat(init): use user configured npm registry (#4937)
Browse files Browse the repository at this point in the history
Query your configured npm registry for Stryker plugins instead of the default one. If no registry is configured, fall back on the default.
  • Loading branch information
Tamir-M authored Aug 22, 2024
1 parent f91ac10 commit 33e348e
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 14 deletions.
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))
.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 };
85 changes: 85 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,85 @@
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;

beforeEach(() => {
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.calledOnceWithExactly(execaCommandSyncMock, 'npm config get --global registry', {
stdout: 'pipe',
timeout: 20000,
});

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.calledOnceWithExactly(execaCommandSyncMock, 'npm config get --global registry', {
stdout: 'pipe',
timeout: 20000,
});

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.',
);
});
});
});
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

0 comments on commit 33e348e

Please sign in to comment.