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: allow to set a custom bit roots directory #9053

Merged
merged 10 commits into from
Jul 23, 2024
21 changes: 21 additions & 0 deletions e2e/harmony/root-components.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getRootComponentDir } from '@teambit/workspace.root-components';
import { resolveFrom } from '@teambit/toolbox.modules.module-resolver';
import chai, { expect } from 'chai';
import fs from 'fs-extra';
Expand Down Expand Up @@ -1679,3 +1680,23 @@ describe('create with root components on', function () {
expect(path.join(helper.env.rootCompDirDep('teambit.react/react', 'my-button'), 'index.ts')).to.be.a.path();
});
});

describe('custom root components directory', function () {
let helper: Helper;
this.timeout(0);
describe('set a valid custom location', () => {
before(() => {
helper = new Helper();
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.extensions.workspaceJsonc.addKeyValToWorkspace('rootComponentsDirectory', '');
helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('rootComponents', true);
helper.command.create('react', 'card', '--env teambit.react/react');
helper.command.install();
});
it('should create the root component directory at the specified location', () => {
expect(
getRootComponentDir(path.join(helper.scopes.localPath, '.bit_roots'), 'teambit.react/react')
).to.be.a.path();
});
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@
"@teambit/harmony.modules.feature-toggle": "~0.0.4",
"@teambit/legacy.scope-api": "~0.0.1",
"@teambit/bit-error": "~0.0.404",
"@teambit/bit-roots": "~0.0.133",
"@teambit/lane-id": "~0.0.311",
"@teambit/component-version": "^1.0.3",
"@teambit/toolbox.network.agent": "~0.0.554",
"@teambit/legacy-component-log": "~0.0.402",
"@teambit/graph.cleargraph": "0.0.11",
"@teambit/workspace.root-components": "1.0.0",
"@babel/core": "7.19.6",
"@babel/runtime": "7.23.2",
"@teambit/legacy-bit-id": "^1.1.1",
Expand Down
457 changes: 326 additions & 131 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions scopes/compilation/compiler/workspace-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { DependencyResolverMain } from '@teambit/dependency-resolver';
import { PathOsBasedAbsolute, PathOsBasedRelative } from '@teambit/toolbox.path.path';
import { componentIdToPackageName } from '@teambit/pkg.modules.component-package-name';
import { UiMain } from '@teambit/ui';
import { readBitRootsDir } from '@teambit/bit-roots';
import { readRootComponentsDir } from '@teambit/workspace.root-components';
import { groupBy, uniq } from 'lodash';
import type { PreStartOpts } from '@teambit/ui';
import { MultiCompiler } from '@teambit/multi-compiler';
Expand Down Expand Up @@ -148,7 +148,7 @@ ${this.compileErrors.map(formatError).join('\n')}`);
const injectedDirs = await this.workspace.getInjectedDirs(this.component);
if (injectedDirs.length > 0) return injectedDirs;

const rootDirs = await readBitRootsDir(this.workspace.path);
const rootDirs = await readRootComponentsDir(this.workspace.rootComponentsPath);
return rootDirs.map((rootDir) => path.relative(this.workspace.path, path.join(rootDir, packageName)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import multimatch from 'multimatch';
import mapSeries from 'p-map-series';
import { MainRuntime } from '@teambit/cli';
import { getAllCoreAspectsIds } from '@teambit/bit';
import { getRelativeRootComponentDir } from '@teambit/bit-roots';
import { getRootComponentDir } from '@teambit/workspace.root-components';
import { ComponentAspect, Component, ComponentMap, ComponentMain, IComponent } from '@teambit/component';
import type { ConfigMain } from '@teambit/config';
import { join } from 'path';
import { join, relative } from 'path';
import { compact, get, pick, uniq, omit, cloneDeep } from 'lodash';
import { ConfigAspect } from '@teambit/config';
import { EnvsAspect } from '@teambit/envs';
Expand Down Expand Up @@ -506,14 +506,23 @@ export class DependencyResolverMain {
* Returns the location where the component is installed with its peer dependencies
* This is used in cases you want to actually run the components and make sure all the dependencies (especially peers) are resolved correctly
*/
getRuntimeModulePath(component: Component, isInWorkspace = false) {
getRuntimeModulePath(
component: Component,
options: {
workspacePath: string;
rootComponentsPath: string;
isInWorkspace?: boolean;
}
) {
if (!this.hasRootComponents()) {
const modulePath = this.getModulePath(component);
return modulePath;
}
const pkgName = this.getPackageName(component);
const rootComponentsRelativePath = relative(options.workspacePath, options.rootComponentsPath);
const getRelativeRootComponentDir = getRootComponentDir.bind(null, rootComponentsRelativePath ?? '');
const selfRootDir = getRelativeRootComponentDir(
!isInWorkspace ? component.id.toString() : component.id.toStringWithoutVersion()
options.isInWorkspace ? component.id.toStringWithoutVersion() : component.id.toString()
);
// In case the component is it's own root we want to load it from it's own root folder
if (fs.pathExistsSync(selfRootDir)) {
Expand Down
11 changes: 7 additions & 4 deletions scopes/dependencies/pnpm/lynx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
PackageManagerNetworkConfig,
} from '@teambit/dependency-resolver';
import { BitError } from '@teambit/bit-error';
import { BIT_ROOTS_DIR } from '@teambit/legacy/dist/constants';
import {
MutatedProject,
mutateModules,
Expand All @@ -30,7 +31,7 @@ import { restartWorkerPool, finishWorkers } from '@pnpm/worker';
import { createPkgGraph } from '@pnpm/workspace.pkgs-graph';
import { PackageManifest, ProjectManifest, ReadPackageHook } from '@pnpm/types';
import { Logger } from '@teambit/logger';
import { VIRTUAL_STORE_DIR_MAX_LENGTH } from '@teambit/dependencies.pnpm.dep-path'
import { VIRTUAL_STORE_DIR_MAX_LENGTH } from '@teambit/dependencies.pnpm.dep-path';
import toNerfDart from 'nerf-dart';
import { pnpmErrorToBitError } from './pnpm-error-to-bit-error';
import { readConfig } from './read-config';
Expand Down Expand Up @@ -410,7 +411,7 @@ function readPackageHook(pkg: PackageManifest, workspaceDir?: string): PackageMa
return pkg;
}
// workspaceDir is set only for workspace packages
if (workspaceDir && !workspaceDir.includes('.bit_roots')) {
if (workspaceDir && !workspaceDir.includes(BIT_ROOTS_DIR)) {
return readWorkspacePackageHook(pkg);
}
return readDependencyPackageHook(pkg);
Expand Down Expand Up @@ -458,8 +459,10 @@ function readWorkspacePackageHook(pkg: PackageManifest): PackageManifest {
}

function groupPkgs(manifestsByPaths: Record<string, ProjectManifest>, opts: { update?: boolean }) {
const pkgs = Object.entries(manifestsByPaths)
.map(([rootDir, manifest]) => ({ rootDir: rootDir as ProjectRootDir, manifest }));
const pkgs = Object.entries(manifestsByPaths).map(([rootDir, manifest]) => ({
rootDir: rootDir as ProjectRootDir,
manifest,
}));
const { graph } = createPkgGraph(pkgs);
const chunks = sortPackages(graph as any);

Expand Down
3 changes: 2 additions & 1 deletion scopes/dependencies/pnpm/pnpm.package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
import { renderTree } from '@pnpm/list';
import { readWantedLockfile } from '@pnpm/lockfile-file';
import { ProjectManifest } from '@pnpm/types';
import { BIT_ROOTS_DIR } from '@teambit/legacy/dist/constants';
import { join } from 'path';
import { readConfig } from './read-config';
import { pnpmPruneModules } from './pnpm-prune-modules';
Expand Down Expand Up @@ -305,7 +306,7 @@ export class PnpmPackageManager implements PackageManager {
const search = createPackagesSearcher([depName]);
const lockfile = await readWantedLockfile(opts.lockfileDir, { ignoreIncompatible: false });
const projectPaths = Object.keys(lockfile?.importers ?? {})
.filter((id) => !id.startsWith('node_modules/.bit_roots'))
.filter((id) => !id.includes(`${BIT_ROOTS_DIR}/`))
.map((id) => join(opts.lockfileDir, id));
const cache = new Map();
const modulesManifest = await this._readModulesManifest(opts.lockfileDir);
Expand Down
5 changes: 4 additions & 1 deletion scopes/pkg/pkg/pkg.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,10 @@ export class PkgMain {
* This is used in cases you want to actually run the components and make sure all the dependencies (especially peers) are resolved correctly
*/
getRuntimeModulePath(component: Component, options: GetModulePathOptions = {}) {
const relativePath = this.dependencyResolver.getRuntimeModulePath(component);
const relativePath = this.dependencyResolver.getRuntimeModulePath(component, {
workspacePath: this.workspace.path,
rootComponentsPath: this.workspace.rootComponentsPath,
Copy link
Member

Choose a reason for hiding this comment

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

don't you need the isInWorkspace prop here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I don't need it as it wasn't here before my changes.

});
if (options?.absPath) {
if (this.workspace) {
return join(this.workspace.path, relativePath);
Expand Down
26 changes: 14 additions & 12 deletions scopes/workspace/install/install.main.runtime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs, { pathExists } from 'fs-extra';
import path from 'path';
import { getRootComponentDir, getBitRootsDir, linkPkgsToBitRoots } from '@teambit/bit-roots';
import { getRootComponentDir, linkPkgsToRootComponents } from '@teambit/workspace.root-components';
import { CompilerMain, CompilerAspect, CompilationInitiator } from '@teambit/compiler';
import { CLIMain, CommandList, CLIAspect, MainRuntime } from '@teambit/cli';
import chalk from 'chalk';
Expand Down Expand Up @@ -614,11 +614,10 @@ export class InstallMain {

private async _updateRootDirs(rootDirs: string[]) {
try {
const bitRootCompsDir = getBitRootsDir(this.workspace.path);
const existingDirs = await fs.readdir(bitRootCompsDir);
const existingDirs = await fs.readdir(this.workspace.rootComponentsPath);
await Promise.all(
existingDirs.map(async (dirName) => {
const dirPath = path.join(bitRootCompsDir, dirName);
const dirPath = path.join(this.workspace.rootComponentsPath, dirName);
if (!rootDirs.includes(dirPath)) {
await fs.remove(dirPath);
}
Expand Down Expand Up @@ -656,7 +655,7 @@ export class InstallMain {
await Promise.all(
envs.map(async (envId) => {
return [
await this.getRootComponentDirByRootId(this.workspace.path, envId),
await this.getRootComponentDirByRootId(this.workspace.rootComponentsPath, envId),
{
dependencies: {
...(await this._getEnvDependencies(envId)),
Expand Down Expand Up @@ -718,7 +717,7 @@ export class InstallMain {
if (!appManifest) return null;
const envId = await this.envs.calculateEnvId(app);
return [
await this.getRootComponentDirByRootId(this.workspace.path, app.id),
await this.getRootComponentDirByRootId(this.workspace.rootComponentsPath, app.id),
{
...omit(appManifest, ['name', 'version']),
dependencies: {
Expand Down Expand Up @@ -927,23 +926,26 @@ export class InstallMain {
const apps = (await this.app.listAppsComponents()).map((component) => component.id);
await Promise.all(
[...envs, ...apps].map(async (id) => {
const dir = await this.getRootComponentDirByRootId(this.workspace.path, id);
const dir = await this.getRootComponentDirByRootId(this.workspace.rootComponentsPath, id);
await fs.mkdirp(dir);
})
);
await linkPkgsToBitRoots(
this.workspace.path,
await linkPkgsToRootComponents(
{
rootComponentsPath: this.workspace.rootComponentsPath,
workspacePath: this.workspace.path,
},
compDirMap.components.map((component) => this.dependencyResolver.getPackageName(component))
);
}

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

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs-extra';
import pMapSeries from 'p-map-series';
import * as path from 'path';
import { linkPkgsToBitRoots } from '@teambit/bit-roots';
import { linkPkgsToRootComponents } from '@teambit/workspace.root-components';
import { ComponentID } from '@teambit/component-id';
import { IS_WINDOWS, PACKAGE_JSON, SOURCE_DIR_SYMLINK_TO_NM } from '@teambit/legacy/dist/constants';
import { BitMap } from '@teambit/legacy.bit-map';
Expand Down Expand Up @@ -69,8 +69,11 @@ export default class NodeModuleLinker {
this.workspace.clearAllComponentsCache();
}

await linkPkgsToBitRoots(
workspacePath,
await linkPkgsToRootComponents(
{
rootComponentsPath: this.workspace.rootComponentsPath,
workspacePath,
},
this.components.map((comp) => componentIdToPackageName(comp.state._consumer))
);
return linksResults;
Expand Down
6 changes: 6 additions & 0 deletions scopes/workspace/workspace/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export interface WorkspaceExtConfig {
*/
defaultDirectory: string;

/**
* sets the location of the root components directory.
* The location is a relative path to the workspace root and should use linux path separators (/).
*/
rootComponentsDirectory?: string;

/**
* set the default structure of components in your project
*/
Expand Down
19 changes: 17 additions & 2 deletions scopes/workspace/workspace/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { CompIdGraph, DepEdgeType } from '@teambit/graph';
import { slice, isEmpty, merge, compact, uniqBy } from 'lodash';
import {
MergeConfigFilename,
BIT_ROOTS_DIR,
CFG_DEFAULT_RESOLVE_ENVS_FROM_ROOTS,
CFG_USER_TOKEN_KEY,
} from '@teambit/legacy/dist/constants';
Expand Down Expand Up @@ -286,6 +287,17 @@ export class Workspace implements ComponentFactory {
return this.consumer.getPath();
}

/**
* Get the location of the bit roots folder
*/
get rootComponentsPath() {
const baseDir =
this.config.rootComponentsDirectory != null
? path.join(this.path, this.config.rootComponentsDirectory)
: this.modulesPath;
return path.join(baseDir, BIT_ROOTS_DIR);
}

/** get the `node_modules` folder of this workspace */
private get modulesPath() {
return path.join(this.path, 'node_modules');
Expand Down Expand Up @@ -1707,8 +1719,11 @@ the following envs are used in this workspace: ${availableEnvs.join(', ')}`);
}

async getComponentPackagePath(component: Component) {
const inInWs = await this.hasId(component.id);
const relativePath = this.dependencyResolver.getRuntimeModulePath(component, inInWs);
const relativePath = this.dependencyResolver.getRuntimeModulePath(component, {
workspacePath: this.path,
rootComponentsPath: this.rootComponentsPath,
isInWorkspace: this.hasId(component.id),
});
return path.join(this.path, relativePath);
}

Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export const BIT_MAP = '.bitmap';

export const OLD_BIT_MAP = '.bit.map.json';

export const BIT_ROOTS_DIR = '.bit_roots';

export const TESTS_FORK_LEVEL = {
NONE: 'NONE',
ONE: 'ONE',
Expand Down
4 changes: 2 additions & 2 deletions src/e2e-helper/e2e-env-helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from 'path';
import { getRootComponentDir } from '@teambit/bit-roots';
import { getRootComponentDir } from '@teambit/workspace.root-components';
import CommandHelper from './e2e-command-helper';
import ExtensionsHelper from './e2e-extensions-helper';
import FixtureHelper, { GenerateEnvJsoncOptions } from './e2e-fixtures-helper';
Expand Down Expand Up @@ -47,7 +47,7 @@ export default class EnvHelper {
}

rootCompDir(envName: string) {
return getRootComponentDir(this.scopes.localPath, envName);
return getRootComponentDir(path.join(this.scopes.localPath, 'node_modules/.bit_roots'), envName);
}

getTypeScriptSettingsForES5() {
Expand Down