Skip to content

Commit c8fedf9

Browse files
authored
feat: allow to set a custom bit roots directory (teambit#9028)
## Proposed Changes - - -
1 parent bf3e096 commit c8fedf9

File tree

12 files changed

+477
-172
lines changed

12 files changed

+477
-172
lines changed

e2e/harmony/root-components.e2e.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getRootComponentDir } from '@teambit/workspace.root-components';
12
import { resolveFrom } from '@teambit/toolbox.modules.module-resolver';
23
import chai, { expect } from 'chai';
34
import fs from 'fs-extra';
@@ -1679,3 +1680,36 @@ describe('create with root components on', function () {
16791680
expect(path.join(helper.env.rootCompDirDep('teambit.react/react', 'my-button'), 'index.ts')).to.be.a.path();
16801681
});
16811682
});
1683+
1684+
describe('custom root components directory', function () {
1685+
let helper: Helper;
1686+
this.timeout(0);
1687+
describe('set a valid custom location', () => {
1688+
before(() => {
1689+
helper = new Helper();
1690+
helper.scopeHelper.setNewLocalAndRemoteScopes();
1691+
helper.extensions.workspaceJsonc.addKeyValToWorkspace('rootComponentsDirectory', '__bit_roots__');
1692+
helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('rootComponents', true);
1693+
helper.command.create('react', 'card', '--env teambit.react/react');
1694+
helper.command.install();
1695+
});
1696+
it('should create the root component directory at the specified location', () => {
1697+
expect(
1698+
getRootComponentDir(path.join(helper.scopes.localPath, '__bit_roots__'), 'teambit.react/react')
1699+
).to.be.a.path();
1700+
});
1701+
});
1702+
describe('set an invalid custom location', () => {
1703+
before(() => {
1704+
helper = new Helper();
1705+
helper.scopeHelper.setNewLocalAndRemoteScopes();
1706+
helper.extensions.workspaceJsonc.addKeyValToWorkspace('rootComponentsDirectory', '');
1707+
helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('rootComponents', true);
1708+
});
1709+
it('should create the root component directory at the specified location', () => {
1710+
expect(() => helper.command.install()).throws(
1711+
'rootComponentsDirectory cannot be empty. Root components directory location cannot be the same as the workspace directory path'
1712+
);
1713+
});
1714+
});
1715+
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@
137137
"@teambit/harmony.modules.feature-toggle": "~0.0.4",
138138
"@teambit/legacy.scope-api": "~0.0.1",
139139
"@teambit/bit-error": "~0.0.404",
140-
"@teambit/bit-roots": "~0.0.133",
141140
"@teambit/lane-id": "~0.0.311",
142141
"@teambit/component-version": "^1.0.3",
143142
"@teambit/toolbox.network.agent": "~0.0.554",
144143
"@teambit/legacy-component-log": "~0.0.402",
145144
"@teambit/graph.cleargraph": "0.0.11",
145+
"@teambit/workspace.root-components": "1.0.0",
146146
"@babel/core": "7.19.6",
147147
"@babel/runtime": "7.23.2",
148148
"@teambit/legacy-bit-id": "^1.1.1",

pnpm-lock.yaml

Lines changed: 366 additions & 143 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scopes/compilation/compiler/workspace-compiler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { DependencyResolverMain } from '@teambit/dependency-resolver';
2222
import { PathOsBasedAbsolute, PathOsBasedRelative } from '@teambit/toolbox.path.path';
2323
import { componentIdToPackageName } from '@teambit/pkg.modules.component-package-name';
2424
import { UiMain } from '@teambit/ui';
25-
import { readBitRootsDir } from '@teambit/bit-roots';
25+
import { readRootComponentsDir } from '@teambit/workspace.root-components';
2626
import { groupBy, uniq } from 'lodash';
2727
import type { PreStartOpts } from '@teambit/ui';
2828
import { MultiCompiler } from '@teambit/multi-compiler';
@@ -148,7 +148,7 @@ ${this.compileErrors.map(formatError).join('\n')}`);
148148
const injectedDirs = await this.workspace.getInjectedDirs(this.component);
149149
if (injectedDirs.length > 0) return injectedDirs;
150150

151-
const rootDirs = await readBitRootsDir(this.workspace.path);
151+
const rootDirs = await readRootComponentsDir(this.workspace.rootComponentsPath);
152152
return rootDirs.map((rootDir) => path.relative(this.workspace.path, path.join(rootDir, packageName)));
153153
}
154154

scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import multimatch from 'multimatch';
22
import mapSeries from 'p-map-series';
33
import { MainRuntime } from '@teambit/cli';
44
import { getAllCoreAspectsIds } from '@teambit/bit';
5-
import { getRelativeRootComponentDir } from '@teambit/bit-roots';
5+
import { getRootComponentDir } from '@teambit/workspace.root-components';
66
import { ComponentAspect, Component, ComponentMap, ComponentMain, IComponent } from '@teambit/component';
77
import type { ConfigMain } from '@teambit/config';
8-
import { join } from 'path';
8+
import { join, relative } from 'path';
99
import { compact, get, pick, uniq, omit, cloneDeep } from 'lodash';
1010
import { ConfigAspect } from '@teambit/config';
1111
import { EnvsAspect } from '@teambit/envs';
@@ -502,18 +502,36 @@ export class DependencyResolverMain {
502502
return componentIdToPackageName(component.state._consumer);
503503
}
504504

505+
/*
506+
* Returns the location where the component is installed with its peer dependencies inside a workspace.
507+
* This is used in cases you want to actually run the components and make sure all the dependencies (especially peers) are resolved correctly
508+
*/
509+
getRuntimeModulePathInWorkspace(component: Component, opts: { workspacePath: string; rootComponentsPath: string }) {
510+
const rootComponentsRelativePath = relative(opts.workspacePath, opts.rootComponentsPath);
511+
return this._getRuntimeModulePath(component, rootComponentsRelativePath);
512+
}
513+
514+
/*
515+
* Returns the location where the component is installed with its peer dependencies inside capsules.
516+
* This is used in cases you want to actually run the components and make sure all the dependencies (especially peers) are resolved correctly
517+
*/
518+
getRuntimeModulePathInCapsules(component: Component) {
519+
return this._getRuntimeModulePath(component);
520+
}
521+
505522
/*
506523
* Returns the location where the component is installed with its peer dependencies
507524
* This is used in cases you want to actually run the components and make sure all the dependencies (especially peers) are resolved correctly
508525
*/
509-
getRuntimeModulePath(component: Component, isInWorkspace = false) {
526+
_getRuntimeModulePath(component: Component, rootComponentsRelativePath?: string) {
510527
if (!this.hasRootComponents()) {
511528
const modulePath = this.getModulePath(component);
512529
return modulePath;
513530
}
514531
const pkgName = this.getPackageName(component);
532+
const getRelativeRootComponentDir = getRootComponentDir.bind(null, rootComponentsRelativePath ?? '');
515533
const selfRootDir = getRelativeRootComponentDir(
516-
!isInWorkspace ? component.id.toString() : component.id.toStringWithoutVersion()
534+
rootComponentsRelativePath == null ? component.id.toString() : component.id.toStringWithoutVersion()
517535
);
518536
// In case the component is it's own root we want to load it from it's own root folder
519537
if (fs.pathExistsSync(selfRootDir)) {

scopes/harmony/cli-reference/cli-reference.docs.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@
22
description: 'Bit command synopses. Bit version: 1.7.48'
33
labels: ['cli', 'mdx', 'docs']
44
---
5-

scopes/pkg/pkg/pkg.main.runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export class PkgMain {
206206
* This is used in cases you want to actually run the components and make sure all the dependencies (especially peers) are resolved correctly
207207
*/
208208
getRuntimeModulePath(component: Component, options: GetModulePathOptions = {}) {
209-
const relativePath = this.dependencyResolver.getRuntimeModulePath(component);
209+
const relativePath = this.dependencyResolver.getRuntimeModulePathInCapsules(component);
210210
if (options?.absPath) {
211211
if (this.workspace) {
212212
return join(this.workspace.path, relativePath);

scopes/workspace/install/install.main.runtime.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs, { pathExists } from 'fs-extra';
22
import path from 'path';
3-
import { getRootComponentDir, getBitRootsDir, linkPkgsToBitRoots } from '@teambit/bit-roots';
3+
import { getRootComponentDir, linkPkgsToRootComponents } from '@teambit/workspace.root-components';
44
import { CompilerMain, CompilerAspect, CompilationInitiator } from '@teambit/compiler';
55
import { CLIMain, CommandList, CLIAspect, MainRuntime } from '@teambit/cli';
66
import chalk from 'chalk';
@@ -614,11 +614,10 @@ export class InstallMain {
614614

615615
private async _updateRootDirs(rootDirs: string[]) {
616616
try {
617-
const bitRootCompsDir = getBitRootsDir(this.workspace.path);
618-
const existingDirs = await fs.readdir(bitRootCompsDir);
617+
const existingDirs = await fs.readdir(this.workspace.rootComponentsPath);
619618
await Promise.all(
620619
existingDirs.map(async (dirName) => {
621-
const dirPath = path.join(bitRootCompsDir, dirName);
620+
const dirPath = path.join(this.workspace.rootComponentsPath, dirName);
622621
if (!rootDirs.includes(dirPath)) {
623622
await fs.remove(dirPath);
624623
}
@@ -656,7 +655,7 @@ export class InstallMain {
656655
await Promise.all(
657656
envs.map(async (envId) => {
658657
return [
659-
await this.getRootComponentDirByRootId(this.workspace.path, envId),
658+
await this.getRootComponentDirByRootId(this.workspace.rootComponentsPath, envId),
660659
{
661660
dependencies: {
662661
...(await this._getEnvDependencies(envId)),
@@ -718,7 +717,7 @@ export class InstallMain {
718717
if (!appManifest) return null;
719718
const envId = await this.envs.calculateEnvId(app);
720719
return [
721-
await this.getRootComponentDirByRootId(this.workspace.path, app.id),
720+
await this.getRootComponentDirByRootId(this.workspace.rootComponentsPath, app.id),
722721
{
723722
...omit(appManifest, ['name', 'version']),
724723
dependencies: {
@@ -927,23 +926,26 @@ export class InstallMain {
927926
const apps = (await this.app.listAppsComponents()).map((component) => component.id);
928927
await Promise.all(
929928
[...envs, ...apps].map(async (id) => {
930-
const dir = await this.getRootComponentDirByRootId(this.workspace.path, id);
929+
const dir = await this.getRootComponentDirByRootId(this.workspace.rootComponentsPath, id);
931930
await fs.mkdirp(dir);
932931
})
933932
);
934-
await linkPkgsToBitRoots(
935-
this.workspace.path,
933+
await linkPkgsToRootComponents(
934+
{
935+
rootComponentsPath: this.workspace.rootComponentsPath,
936+
workspacePath: this.workspace.path,
937+
},
936938
compDirMap.components.map((component) => this.dependencyResolver.getPackageName(component))
937939
);
938940
}
939941

940-
private async getRootComponentDirByRootId(workspacePath: string, rootComponentId: ComponentID): Promise<string> {
942+
private async getRootComponentDirByRootId(rootComponentsPath: string, rootComponentId: ComponentID): Promise<string> {
941943
// Root directories for local envs and apps are created without their version number.
942944
// This is done in order to avoid changes to the lockfile after such components are tagged.
943-
const id = (await this.workspace.hasId(rootComponentId))
945+
const id = this.workspace.hasId(rootComponentId)
944946
? rootComponentId.toStringWithoutVersion()
945947
: rootComponentId.toString();
946-
return getRootComponentDir(workspacePath, id);
948+
return getRootComponentDir(rootComponentsPath, id);
947949
}
948950

949951
/**

scopes/workspace/modules/node-modules-linker/node-modules-linker.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'fs-extra';
22
import pMapSeries from 'p-map-series';
33
import * as path from 'path';
4-
import { linkPkgsToBitRoots } from '@teambit/bit-roots';
4+
import { linkPkgsToRootComponents } from '@teambit/workspace.root-components';
55
import { ComponentID } from '@teambit/component-id';
66
import { IS_WINDOWS, PACKAGE_JSON, SOURCE_DIR_SYMLINK_TO_NM } from '@teambit/legacy/dist/constants';
77
import { BitMap } from '@teambit/legacy.bit-map';
@@ -69,8 +69,11 @@ export default class NodeModuleLinker {
6969
this.workspace.clearAllComponentsCache();
7070
}
7171

72-
await linkPkgsToBitRoots(
73-
workspacePath,
72+
await linkPkgsToRootComponents(
73+
{
74+
rootComponentsPath: this.workspace.rootComponentsPath,
75+
workspacePath,
76+
},
7477
this.components.map((comp) => componentIdToPackageName(comp.state._consumer))
7578
);
7679
return linksResults;

scopes/workspace/workspace/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export interface WorkspaceExtConfig {
2323
*/
2424
defaultDirectory: string;
2525

26+
/**
27+
* sets the location of the root components directory.
28+
* The location is a relative path to the workspace root and should use linux path separators (/).
29+
*/
30+
rootComponentsDirectory?: string;
31+
2632
/**
2733
* set the default structure of components in your project
2834
*/

scopes/workspace/workspace/workspace.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ export class Workspace implements ComponentFactory {
276276
);
277277
const defaultScope = this.config.defaultScope;
278278
if (!defaultScope) throw new BitError('defaultScope is missing');
279+
if (this.config.rootComponentsDirectory === '') {
280+
throw new BitError(
281+
'rootComponentsDirectory cannot be empty. Root components directory location cannot be the same as the workspace directory path'
282+
);
283+
}
279284
if (!isValidScopeName(defaultScope)) throw new InvalidScopeName(defaultScope);
280285
}
281286

@@ -286,6 +291,13 @@ export class Workspace implements ComponentFactory {
286291
return this.consumer.getPath();
287292
}
288293

294+
/**
295+
* Get the location of the bit roots folder
296+
*/
297+
get rootComponentsPath() {
298+
return this.config.rootComponentsDirectory ?? path.join(this.modulesPath, '.bit_roots');
299+
}
300+
289301
/** get the `node_modules` folder of this workspace */
290302
private get modulesPath() {
291303
return path.join(this.path, 'node_modules');
@@ -1706,9 +1718,17 @@ the following envs are used in this workspace: ${availableEnvs.join(', ')}`);
17061718
}
17071719

17081720
async getComponentPackagePath(component: Component) {
1709-
const inInWs = await this.hasId(component.id);
1710-
const relativePath = this.dependencyResolver.getRuntimeModulePath(component, inInWs);
1711-
return path.join(this.path, relativePath);
1721+
return path.join(this.path, await this._getComponentRelativePath(component));
1722+
}
1723+
1724+
async _getComponentRelativePath(component: Component) {
1725+
if (this.hasId(component.id)) {
1726+
return this.dependencyResolver.getRuntimeModulePathInWorkspace(component, {
1727+
workspacePath: this.path,
1728+
rootComponentsPath: this.rootComponentsPath,
1729+
});
1730+
}
1731+
return this.dependencyResolver.getRuntimeModulePathInCapsules(component);
17121732
}
17131733

17141734
// TODO: should we return here the dir as it defined (aka components) or with /{name} prefix (as it used in legacy)

src/e2e-helper/e2e-env-helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as path from 'path';
2-
import { getRootComponentDir } from '@teambit/bit-roots';
2+
import { getRootComponentDir } from '@teambit/workspace.root-components';
33
import CommandHelper from './e2e-command-helper';
44
import ExtensionsHelper from './e2e-extensions-helper';
55
import FixtureHelper, { GenerateEnvJsoncOptions } from './e2e-fixtures-helper';
@@ -47,7 +47,7 @@ export default class EnvHelper {
4747
}
4848

4949
rootCompDir(envName: string) {
50-
return getRootComponentDir(this.scopes.localPath, envName);
50+
return getRootComponentDir(path.join(this.scopes.localPath, 'node_modules/.bit_roots'), envName);
5151
}
5252

5353
getTypeScriptSettingsForES5() {

0 commit comments

Comments
 (0)