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

make sure a basic flow of defaultScope functionality is working #2313

Merged
merged 1 commit into from
Feb 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/commands/link.e2e.1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('bit link', function() {
).to.be.a.directory();
});
});
describe('when scopeDefault is overridden for this component', () => {
describe('when defaultScope is overridden for this component', () => {
let linkOutput;
before(() => {
helper.scopeHelper.getClonedLocalScope(beforeLink);
Expand Down
51 changes: 51 additions & 0 deletions e2e/functionalities/default-scope.e2e.2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import chai, { expect } from 'chai';
import Helper from '../../src/e2e-helper/e2e-helper';

chai.use(require('chai-fs'));

describe('default scope functionality', function() {
this.timeout(0);
const helper = new Helper();
after(() => {
helper.scopeHelper.destroy();
});
describe('basic flow', () => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.fixtures.populateWorkspaceWithThreeComponentsAndModulePath();
helper.bitJson.addDefaultScope();
helper.command.runCmd('bit link');
});
it('bit status should not break', () => {
const status = helper.command.statusJson();
expect(status.newComponents).have.lengthOf(3);
expect(status.invalidComponents).have.lengthOf(0);
});
describe('tagging the components', () => {
let tagOutput;
before(() => {
tagOutput = helper.command.tagAllComponents();
});
it('should be able to to tag them successfully', () => {
expect(tagOutput).to.have.string('tagged');
});
it('bit status should not show any issue', () => {
const status = helper.command.statusJson();
expect(status.stagedComponents).have.lengthOf(3);
expect(status.newComponents).have.lengthOf(0);
expect(status.modifiedComponent).have.lengthOf(0);
expect(status.invalidComponents).have.lengthOf(0);
});
describe('exporting the components', () => {
before(() => {
helper.command.exportAllComponents();
});
it('should be able to export them all successfully', () => {
const status = helper.command.statusJson();
expect(status.stagedComponents).have.lengthOf(0);
expect(status.newComponents).have.lengthOf(0);
expect(status.modifiedComponent).have.lengthOf(0);
expect(status.invalidComponents).have.lengthOf(0);
});
});
});
});
40 changes: 13 additions & 27 deletions src/consumer/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import ComponentsPendingImport from './component-ops/exceptions/components-pendi
import { AutoTagResult } from '../scope/component-ops/auto-tag';
import ShowDoctorError from '../error/show-doctor-error';
import { EnvType } from '../extensions/env-extension-types';
import { packageNameToComponentId } from '../utils/bit/package-name-to-component-id';

type ConsumerProps = {
projectPath: string;
Expand Down Expand Up @@ -699,34 +700,19 @@ export default class Consumer {

getComponentIdFromNodeModulesPath(requirePath: string, bindingPrefix: string): BitId {
requirePath = pathNormalizeToLinux(requirePath);
// Temp fix to support old components before the migration has been running
bindingPrefix = bindingPrefix === 'bit' ? '@bit' : bindingPrefix;
const prefix = requirePath.includes('node_modules') ? `node_modules/${bindingPrefix}/` : `${bindingPrefix}/`;
const withoutPrefix = requirePath.substr(requirePath.indexOf(prefix) + prefix.length);
const componentName = withoutPrefix.includes('/')
? withoutPrefix.substr(0, withoutPrefix.indexOf('/')) // the part after the first slash is the path inside the package
: withoutPrefix;
const pathSplit = componentName.split(NODE_PATH_COMPONENT_SEPARATOR);
if (pathSplit.length < 2) throw new GeneralError(`component has an invalid require statement: ${requirePath}`);
// since the dynamic namespaces feature introduced, the require statement doesn't have a fixed
// number of separators.
// also, a scope name may or may not include a dot. depends whether it's on bitHub or self hosted.
// we must check against BitMap to get the correct scope and name of the id.
if (pathSplit.length === 2) {
return new BitId({ scope: pathSplit[0], name: pathSplit[1] });
const prefix = requirePath.includes('node_modules') ? 'node_modules/' : '';
const withoutPrefix = prefix ? requirePath.substr(requirePath.indexOf(prefix) + prefix.length) : requirePath;

if (!withoutPrefix.includes('/')) {
throw new GeneralError(
'getComponentIdFromNodeModulesPath expects the path to have at least one slash for the scoped package, such as @bit/'
);
}
const mightBeScope = R.head(pathSplit);
const mightBeName = R.tail(pathSplit).join('/');
const mightBeId = new BitId({ scope: mightBeScope, name: mightBeName });
const allBitIds = this.bitMap.getAllBitIds();
if (allBitIds.searchWithoutVersion(mightBeId)) return mightBeId;
// only bit hub has the concept of having the username in the scope name.
if (bindingPrefix !== 'bit' && bindingPrefix !== '@bit') return mightBeId;
// pathSplit has 3 or more items. the first two are the scope, the rest is the name.
// for example "user.scopeName.utils.is-string" => scope: user.scopeName, name: utils/is-string
const scope = pathSplit.splice(0, 2).join('.');
const name = pathSplit.join('/');
return new BitId({ scope, name });
const packageSplitBySlash = withoutPrefix.split('/');
// the part after the second slash is the path inside the package, just ignore it.
// (e.g. @bit/my-scope.my-name/internal-path.js).
const packageName = `${packageSplitBySlash[0]}/${packageSplitBySlash[1]}`;
return packageNameToComponentId(this, packageName, bindingPrefix);
}

composeRelativeComponentPath(bitId: BitId): string {
Expand Down
3 changes: 3 additions & 0 deletions src/e2e-helper/e2e-bit-json-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export default class BitJsonHelper {
bitJson.overrides = overrides;
this.write(bitJson);
}
addDefaultScope(scope = this.scopes.remote) {
this.addKeyVal(undefined, 'defaultScope', scope);
}
getEnvByType(bitJson: Record<string, any>, envType: 'compiler' | 'tester') {
const basePath = ['env', envType];
const env = R.path(basePath, bitJson);
Expand Down
9 changes: 9 additions & 0 deletions src/e2e-helper/e2e-fixtures-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ export default class FixtureHelper {
this.addComponentBarFoo();
}

populateWorkspaceWithThreeComponentsAndModulePath() {
this.fs.createFile('utils', 'is-type.js', fixtures.isType);
this.addComponentUtilsIsType();
this.fs.createFile('utils', 'is-string.js', fixtures.isStringModulePath(this.scopes.remote));
this.addComponentUtilsIsString();
this.createComponentBarFoo(fixtures.barFooModulePath(this.scopes.remote));
this.addComponentBarFoo();
}

/**
* @deprecated use populateWorkspaceWithThreeComponents()
*/
Expand Down
83 changes: 83 additions & 0 deletions src/utils/bit/package-name-to-component-id.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { expect } from 'chai';
import { packageNameToComponentId } from './package-name-to-component-id';
import { Consumer } from '../../consumer';
import { BitIds, BitId } from '../../bit-id';

describe('packageNameToComponentId', function() {
this.timeout(0);
let consumer: Consumer;
before(() => {
// @ts-ignore
consumer = new Consumer({ projectPath: '', config: {} });
});
it('should parse the path correctly when a component is not in bitMap and has one dot', () => {
const result = packageNameToComponentId(consumer, '@bit/remote.comp', '@bit');
expect(result.scope).to.equal('remote');
expect(result.name).to.equal('comp');
});
it('should parse the path correctly when a component is not in bitMap and has two dots', () => {
const result = packageNameToComponentId(consumer, '@bit/remote.comp.comp2', '@bit');
expect(result.scope).to.equal('remote.comp');
expect(result.name).to.equal('comp2');
});
it('should parse the path correctly when a component is not in bitMap and has three dots', () => {
const result = packageNameToComponentId(consumer, '@bit/remote.comp.comp2.comp3', '@bit');
expect(result.scope).to.equal('remote.comp');
expect(result.name).to.equal('comp2/comp3');
});
describe('with defaultScope', () => {
describe('when the defaultScope has dot', () => {
it('should return bitId without scope when the component is in .bitmap without scope', () => {
// @ts-ignore
consumer.bitMap = { getAllBitIds: () => new BitIds(new BitId({ name: 'bar/foo' })) };
consumer.config.defaultScope = 'bit.utils';
const result = packageNameToComponentId(consumer, '@bit/bit.utils.bar.foo', '@bit');
expect(result.scope).to.be.null;
expect(result.name).to.equal('bar/foo');
});
it('should return bitId with scope when the component is in .bitmap with scope', () => {
// @ts-ignore
consumer.bitMap = { getAllBitIds: () => new BitIds(new BitId({ scope: 'bit.utils', name: 'bar/foo' })) };
consumer.config.defaultScope = 'bit.utils';
const result = packageNameToComponentId(consumer, '@bit/bit.utils.bar.foo', '@bit');
expect(result.scope).to.equal('bit.utils');
expect(result.name).to.equal('bar/foo');
});
it('should return bitId with scope when the component is not .bitmap at all', () => {
// @ts-ignore
consumer.bitMap = { getAllBitIds: () => new BitIds() };
consumer.config.defaultScope = 'bit.utils';
const result = packageNameToComponentId(consumer, '@bit/bit.utils.bar.foo', '@bit');
expect(result.scope).to.equal('bit.utils');
expect(result.name).to.equal('bar/foo');
});
});
describe('when the defaultScope does not have dot', () => {
before(() => {
consumer.config.defaultScope = 'utils';
});
it('should return bitId without scope when the component is in .bitmap without scope', () => {
// @ts-ignore
consumer.bitMap = { getAllBitIds: () => new BitIds(new BitId({ name: 'bar/foo' })) };
const result = packageNameToComponentId(consumer, '@bit/utils.bar.foo', '@bit');
expect(result.scope).to.be.null;
expect(result.name).to.equal('bar/foo');
});
it('should return bitId with scope when the component is in .bitmap with scope', () => {
// @ts-ignore
consumer.bitMap = { getAllBitIds: () => new BitIds(new BitId({ scope: 'utils', name: 'bar/foo' })) };
const result = packageNameToComponentId(consumer, '@bit/utils.bar.foo', '@bit');
expect(result.scope).to.equal('utils');
expect(result.name).to.equal('bar/foo');
});
it('should return bitId with scope when the component is not .bitmap at all', () => {
// @ts-ignore
consumer.bitMap = { getAllBitIds: () => new BitIds() };
const result = packageNameToComponentId(consumer, '@bit/utils.bar.foo', '@bit');
// looks weird, but the default is a dot in the scope.
expect(result.scope).to.equal('utils.bar');
expect(result.name).to.equal('foo');
});
});
});
});
68 changes: 68 additions & 0 deletions src/utils/bit/package-name-to-component-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import R from 'ramda';
import { Consumer } from '../../consumer';
import { BitId } from '../../bit-id';
import { NODE_PATH_COMPONENT_SEPARATOR } from '../../constants';
import GeneralError from '../../error/general-error';

/**
* convert a component package-name to BitId.
* e.g. `@bit/bit.utils/is-string` => { scope: bit.utils, name: is-string }
*/
// eslint-disable-next-line import/prefer-default-export
export function packageNameToComponentId(consumer: Consumer, packageName: string, bindingPrefix: string): BitId {
// Temp fix to support old components before the migration has been running
const prefix = bindingPrefix === 'bit' ? '@bit/' : `${bindingPrefix}/`;
const componentName = packageName.substr(packageName.indexOf(prefix) + prefix.length);

const nameSplit = componentName.split(NODE_PATH_COMPONENT_SEPARATOR);
if (nameSplit.length < 2)
throw new GeneralError(
`package-name is an invalid BitId: ${componentName}, it is missing the scope-name, please set your workspace with a defaultScope`
);
// since the dynamic namespaces feature introduced, the require statement doesn't have a fixed
// number of separators.
// also, a scope name may or may not include a dot. depends whether it's on bitHub or self hosted.
// we must check against BitMap to get the correct scope and name of the id.
if (nameSplit.length === 2) {
return new BitId({ scope: nameSplit[0], name: nameSplit[1] });
}
const defaultScope = consumer.config.defaultScope;
const allBitIds = consumer.bitMap.getAllBitIds();

if (defaultScope && componentName.startsWith(`${defaultScope}.`)) {
const idWithDefaultScope = byDefaultScope(defaultScope, nameSplit);
const bitmapHasExact = allBitIds.hasWithoutVersion(idWithDefaultScope);
if (bitmapHasExact) return idWithDefaultScope;
const idWithoutScope = allBitIds.searchWithoutScopeAndVersion(idWithDefaultScope.changeScope(null));
if (idWithoutScope) return idWithoutScope;
// otherwise, the component is not in .bitmap, continue with other strategies.
}
const mightBeId = createBitIdAssumeScopeDoesNotHaveDot(nameSplit);
if (allBitIds.searchWithoutVersion(mightBeId)) return mightBeId;
// only bit hub has the concept of having the username in the scope name.
if (bindingPrefix !== 'bit' && bindingPrefix !== '@bit') return mightBeId;
// pathSplit has 3 or more items. the first two are the scope, the rest is the name.
// for example "user.scopeName.utils.is-string" => scope: user.scopeName, name: utils/is-string
return createBitIdAssumeScopeHasDot(nameSplit);
}

// scopes on bit.dev always have dot in the name
function createBitIdAssumeScopeHasDot(nameSplit: string[]): BitId {
const nameSplitClone = [...nameSplit];
const scope = nameSplitClone.splice(0, 2).join('.');
const name = nameSplitClone.join('/');
return new BitId({ scope, name });
}

// local scopes (self-hosted) can not have any dot in the name
function createBitIdAssumeScopeDoesNotHaveDot(nameSplit: string[]): BitId {
const scope = R.head(nameSplit);
const name = R.tail(nameSplit).join('/');
return new BitId({ scope, name });
}

function byDefaultScope(defaultScope: string, nameSplit: string[]): BitId {
return defaultScope.includes('.')
? createBitIdAssumeScopeHasDot(nameSplit)
: createBitIdAssumeScopeDoesNotHaveDot(nameSplit);
}