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

Repros: Add ability to generate repros using local registry #18948

Merged
merged 9 commits into from
Aug 17, 2022
35 changes: 31 additions & 4 deletions code/lib/cli/src/js-package-manager/JsPackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export function getPackageDetails(pkg: string): [string, string?] {
return [packageName, packageVersion];
}

interface JsPackageManagerOptions {
cwd?: string;
}
export abstract class JsPackageManager {
public abstract readonly type: 'npm' | 'yarn1' | 'yarn2';

Expand All @@ -36,6 +39,16 @@ export abstract class JsPackageManager {

public abstract getRunCommand(command: string): string;

public abstract setRegistryURL(url: string): void;

public abstract getRegistryURL(): string;

public readonly cwd?: string;

constructor({ cwd }: JsPackageManagerOptions) {
this.cwd = cwd;
}

/**
* Install dependencies listed in `package.json`
*/
Expand All @@ -56,8 +69,12 @@ export abstract class JsPackageManager {
done();
}

packageJsonPath(): string {
return this.cwd ? path.resolve(this.cwd, 'package.json') : path.resolve('package.json');
}

readPackageJson(): PackageJson {
const packageJsonPath = path.resolve('package.json');
const packageJsonPath = this.packageJsonPath();
if (!fs.existsSync(packageJsonPath)) {
throw new Error(`Could not read package.json file at ${packageJsonPath}`);
}
Expand All @@ -68,9 +85,7 @@ export abstract class JsPackageManager {

writePackageJson(packageJson: PackageJson) {
const content = `${JSON.stringify(packageJson, null, 2)}\n`;
const packageJsonPath = path.resolve('package.json');

fs.writeFileSync(packageJsonPath, content, 'utf8');
fs.writeFileSync(this.packageJsonPath(), content, 'utf8');
}

/**
Expand Down Expand Up @@ -326,12 +341,23 @@ export abstract class JsPackageManager {
});
}

public addPackageResolutions(versions: Record<string, string>) {
const packageJson = this.retrievePackageJson();
const resolutions = this.getResolutions(packageJson, versions);
this.writePackageJson({ ...packageJson, ...resolutions });
}

protected abstract runInstall(): void;

protected abstract runAddDeps(dependencies: string[], installAsDevDependencies: boolean): void;

protected abstract runRemoveDeps(dependencies: string[]): void;

protected abstract getResolutions(
packageJson: PackageJson,
versions: Record<string, string>
): Record<string, any>;

/**
* Get the latest or all versions of the input package available on npmjs.com
*
Expand All @@ -346,6 +372,7 @@ export abstract class JsPackageManager {

public executeCommand(command: string, args: string[], stdio?: 'pipe' | 'inherit'): string {
const commandResult = spawnSync(command, args, {
cwd: this.cwd,
stdio: stdio ?? 'pipe',
encoding: 'utf-8',
});
Expand Down
22 changes: 11 additions & 11 deletions code/lib/cli/src/js-package-manager/JsPackageManagerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,35 @@ import { Yarn2Proxy } from './Yarn2Proxy';
import { Yarn1Proxy } from './Yarn1Proxy';

export class JsPackageManagerFactory {
public static getPackageManager(forceNpmUsage = false): JsPackageManager {
public static getPackageManager(forceNpmUsage = false, cwd?: string): JsPackageManager {
if (forceNpmUsage) {
return new NPMProxy();
return new NPMProxy({ cwd });
}

const yarnVersion = getYarnVersion();
const hasYarnLockFile = findUpSync('yarn.lock');
const yarnVersion = getYarnVersion(cwd);
const hasYarnLockFile = findUpSync('yarn.lock', { cwd });

const hasNPMCommand = hasNPM();
const hasNPMCommand = hasNPM(cwd);

if (yarnVersion && (hasYarnLockFile || !hasNPMCommand)) {
return yarnVersion === 1 ? new Yarn1Proxy() : new Yarn2Proxy();
return yarnVersion === 1 ? new Yarn1Proxy({ cwd }) : new Yarn2Proxy({ cwd });
}

if (hasNPMCommand) {
return new NPMProxy();
return new NPMProxy({ cwd });
}

throw new Error('Unable to find a usable package manager within NPM, Yarn and Yarn 2');
}
}

function hasNPM() {
const npmVersionCommand = spawnSync('npm', ['--version']);
function hasNPM(cwd?: string) {
const npmVersionCommand = spawnSync('npm', ['--version'], { cwd });
return npmVersionCommand.status === 0;
}

function getYarnVersion(): 1 | 2 | undefined {
const yarnVersionCommand = spawnSync('yarn', ['--version']);
function getYarnVersion(cwd?: string): 1 | 2 | undefined {
const yarnVersionCommand = spawnSync('yarn', ['--version'], { cwd });

if (yarnVersionCommand.status !== 0) {
return undefined;
Expand Down
41 changes: 41 additions & 0 deletions code/lib/cli/src/js-package-manager/NPMProxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ describe('NPM Proxy', () => {
});
});

describe('setRegistryUrl', () => {
it('should run `npm config set registry https://foo.bar`', () => {
const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('');

npmProxy.setRegistryURL('https://foo.bar');

expect(executeCommandSpy).toHaveBeenCalledWith('npm', [
'config',
'set',
'registry',
'https://foo.bar',
]);
});
});

describe('installDependencies', () => {
describe('npm6', () => {
it('should run `npm install`', () => {
Expand Down Expand Up @@ -204,4 +219,30 @@ describe('NPM Proxy', () => {
expect(version).toEqual(`^${packageVersion}`);
});
});

describe('addPackageResolutions', () => {
it('adds resolutions to package.json and account for existing resolutions', () => {
const writePackageSpy = jest.spyOn(npmProxy, 'writePackageJson').mockImplementation(jest.fn);

jest.spyOn(npmProxy, 'retrievePackageJson').mockImplementation(
jest.fn(() => ({
overrides: {
bar: 'x.x.x',
},
}))
);

const versions = {
foo: 'x.x.x',
};
npmProxy.addPackageResolutions(versions);

expect(writePackageSpy).toHaveBeenCalledWith({
overrides: {
...versions,
bar: 'x.x.x',
},
});
});
});
});
23 changes: 23 additions & 0 deletions code/lib/cli/src/js-package-manager/NPMProxy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import semver from '@storybook/semver';
import { JsPackageManager } from './JsPackageManager';
import type { PackageJson } from './PackageJson';

export class NPMProxy extends JsPackageManager {
readonly type = 'npm';
Expand Down Expand Up @@ -60,6 +61,28 @@ export class NPMProxy extends JsPackageManager {
return this.uninstallArgs;
}

setRegistryURL(url: string) {
if (url) {
this.executeCommand('npm', ['config', 'set', 'registry', url]);
} else {
this.executeCommand('npm', ['config', 'delete', 'registry']);
}
}

getRegistryURL() {
const url = this.executeCommand('npm', ['config', 'get', 'registry']).trim();
return url === 'undefined' ? undefined : url;
}

protected getResolutions(packageJson: PackageJson, versions: Record<string, string>) {
return {
overrides: {
...packageJson.overrides,
...versions,
},
};
}

protected runInstall(): void {
this.executeCommand('npm', this.getInstallArgs(), 'inherit');
}
Expand Down
43 changes: 43 additions & 0 deletions code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ describe('Yarn 1 Proxy', () => {
});
});

describe('setRegistryUrl', () => {
it('should run `yarn config set npmRegistryServer https://foo.bar`', () => {
const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue('');

yarn1Proxy.setRegistryURL('https://foo.bar');

expect(executeCommandSpy).toHaveBeenCalledWith('yarn', [
'config',
'set',
'npmRegistryServer',
'https://foo.bar',
]);
});
});

describe('installDependencies', () => {
it('should run `yarn`', () => {
const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue('');
Expand Down Expand Up @@ -125,4 +140,32 @@ describe('Yarn 1 Proxy', () => {
await expect(yarn1Proxy.latestVersion('@storybook/addons')).rejects.toThrow();
});
});

describe('addPackageResolutions', () => {
it('adds resolutions to package.json and account for existing resolutions', () => {
const writePackageSpy = jest
.spyOn(yarn1Proxy, 'writePackageJson')
.mockImplementation(jest.fn);

jest.spyOn(yarn1Proxy, 'retrievePackageJson').mockImplementation(
jest.fn(() => ({
resolutions: {
bar: 'x.x.x',
},
}))
);

const versions = {
foo: 'x.x.x',
};
yarn1Proxy.addPackageResolutions(versions);

expect(writePackageSpy).toHaveBeenCalledWith({
resolutions: {
...versions,
bar: 'x.x.x',
},
});
});
});
});
23 changes: 23 additions & 0 deletions code/lib/cli/src/js-package-manager/Yarn1Proxy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { JsPackageManager } from './JsPackageManager';
import type { PackageJson } from './PackageJson';

export class Yarn1Proxy extends JsPackageManager {
readonly type = 'yarn1';
Expand All @@ -15,6 +16,28 @@ export class Yarn1Proxy extends JsPackageManager {
return `yarn ${command}`;
}

setRegistryURL(url: string) {
if (url) {
this.executeCommand('yarn', ['config', 'set', 'npmRegistryServer', url]);
} else {
this.executeCommand('yarn', ['config', 'delete', 'npmRegistryServer']);
}
}

getRegistryURL() {
const url = this.executeCommand('yarn', ['config', 'get', 'npmRegistryServer']).trim();
return url === 'undefined' ? undefined : url;
}

protected getResolutions(packageJson: PackageJson, versions: Record<string, string>) {
return {
resolutions: {
...packageJson.resolutions,
...versions,
},
};
}

protected runInstall(): void {
this.executeCommand('yarn', [], 'inherit');
}
Expand Down
43 changes: 43 additions & 0 deletions code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ describe('Yarn 2 Proxy', () => {
});
});

describe('setRegistryUrl', () => {
it('should run `yarn config set npmRegistryServer https://foo.bar`', () => {
const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue('');

yarn2Proxy.setRegistryURL('https://foo.bar');

expect(executeCommandSpy).toHaveBeenCalledWith('yarn', [
'config',
'set',
'npmRegistryServer',
'https://foo.bar',
]);
});
});

describe('addDependencies', () => {
it('with devDep it should run `yarn install -D @storybook/addons`', () => {
const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue('');
Expand Down Expand Up @@ -131,4 +146,32 @@ describe('Yarn 2 Proxy', () => {
await expect(yarn2Proxy.latestVersion('@storybook/addons')).rejects.toThrow();
});
});

describe('addPackageResolutions', () => {
it('adds resolutions to package.json and account for existing resolutions', () => {
const writePackageSpy = jest
.spyOn(yarn2Proxy, 'writePackageJson')
.mockImplementation(jest.fn);

jest.spyOn(yarn2Proxy, 'retrievePackageJson').mockImplementation(
jest.fn(() => ({
resolutions: {
bar: 'x.x.x',
},
}))
);

const versions = {
foo: 'x.x.x',
};
yarn2Proxy.addPackageResolutions(versions);

expect(writePackageSpy).toHaveBeenCalledWith({
resolutions: {
...versions,
bar: 'x.x.x',
},
});
});
});
});
23 changes: 23 additions & 0 deletions code/lib/cli/src/js-package-manager/Yarn2Proxy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { JsPackageManager } from './JsPackageManager';
import type { PackageJson } from './PackageJson';

export class Yarn2Proxy extends JsPackageManager {
readonly type = 'yarn2';
Expand All @@ -15,6 +16,28 @@ export class Yarn2Proxy extends JsPackageManager {
return `yarn ${command}`;
}

setRegistryURL(url: string) {
if (url) {
this.executeCommand('yarn', ['config', 'set', 'npmRegistryServer', url]);
} else {
this.executeCommand('yarn', ['config', 'delete', 'npmRegistryServer']);
}
}

getRegistryURL() {
const url = this.executeCommand('yarn', ['config', 'get', 'npmRegistryServer']).trim();
return url === 'undefined' ? undefined : url;
}

protected getResolutions(packageJson: PackageJson, versions: Record<string, string>) {
return {
resolutions: {
...packageJson.resolutions,
...versions,
},
};
}

protected runInstall(): void {
this.executeCommand('yarn', [], 'inherit');
}
Expand Down
Loading