Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
db94f29
feat(bit-map): allow a component to own the workspace root (rootDir ".")
davidfirst Sep 10, 2026
f0ca113
feat(bit-map): track .bitmap in the workspace-root component
davidfirst Sep 10, 2026
d166385
feat(issues): skip env/compiler-derived issues for the workspace-root…
davidfirst Sep 10, 2026
016b3c4
feat(workspace-root): use the empty env, and keep it out of install a…
davidfirst Sep 10, 2026
b2c0a9d
fix(workspace-root): address review findings on the root component
davidfirst Sep 10, 2026
99c1fd8
fix(component-writer): allow importing the workspace-root component o…
davidfirst Sep 10, 2026
b49b410
chore: merge master into feat/workspace-root-component-nesting
davidfirst Sep 11, 2026
d2b6186
feat(workspace): add trackAllFiles to track package.json and other bi…
davidfirst Sep 11, 2026
442238c
refactor(workspace-root): address review and simplify the root-compon…
davidfirst Sep 11, 2026
189c57f
fix(bit-map): disable globby directory expansion in the scan, it brok…
davidfirst Sep 11, 2026
a6943c1
fix(workspace-root): keep the root out of node_modules, and tighten t…
davidfirst Sep 11, 2026
30c946d
fix(workspace-root): track the root with the empty env as explicit co…
davidfirst Sep 11, 2026
e9b233f
fix(tracker): re-add the workspace root without its name, escape nest…
davidfirst Sep 11, 2026
9984ba1
Merge branch 'master' into feat/workspace-root-component-nesting
davidfirst Sep 11, 2026
1ca46ad
fix(tracker): keep the workspace-root main file safe on bulk tracking…
davidfirst Sep 11, 2026
3bf4c68
fix(workspace-root): require --override for user files on a root impo…
davidfirst Sep 11, 2026
b08e4ef
fix(workspace-root): keep .bitmap in the add-time file-set, skip the …
davidfirst Sep 11, 2026
74c694d
fix(workspace-root): treat symlinks and dirs as import conflicts, app…
davidfirst Sep 11, 2026
6e31b80
fix(workspace-root): honor nested ignore files and skip nested maps i…
davidfirst Sep 14, 2026
6b25177
fix(workspace-root): keep ignore options on an empty map, directory-o…
davidfirst Sep 14, 2026
2320d97
fix(workspace-root): evaluate nested ignore files with the root ones …
davidfirst Sep 14, 2026
e3aace0
fix(workspace-root): keep bit-owned exclusions non-negatable, never s…
davidfirst Sep 14, 2026
8543f90
fix(workspace-root): scan the root on bulk tracking, refuse ordinary …
davidfirst Sep 14, 2026
d002b9a
fix(tracker): scan dotfiles at add time for every component, as the r…
davidfirst Sep 14, 2026
3054c16
fix(bit-map): drop lane state from the versioned map, honor nested ig…
davidfirst Sep 14, 2026
0cbbcdb
fix(tracker): keep the tracked id on bulk re-track, apply the own ign…
davidfirst Sep 14, 2026
8ab2f69
fix(component-writer): accept "--path ." only for a component its ver…
davidfirst Sep 14, 2026
872d041
fix(tracker): guard the workspace-root main file on bulk tracking, ke…
davidfirst Sep 14, 2026
ad5d1a2
fix(workspace-root): refuse symlinked ancestors on a root import, wat…
davidfirst Sep 14, 2026
8a4a106
fix(bit-map): do not follow symlinks in the root scan, resolve the ma…
davidfirst Sep 14, 2026
472549e
fix(tracker): apply the component's own ignore file at add time, as t…
davidfirst Sep 14, 2026
c62e5ee
fix(tracker): guard the workspace-root main file by directory, not by…
davidfirst Sep 14, 2026
176b92c
fix(component-writer): refuse a symlinked destination on a root impor…
davidfirst Sep 14, 2026
9bbf81f
feat(tracker): default the workspace-root main file to workspace.jsonc
davidfirst Sep 14, 2026
71d06d9
test(e2e): assert a second root import is refused on local changes, n…
davidfirst Sep 14, 2026
3eeed3c
fix(pkg): do not report missing node_modules links for the workspace-…
davidfirst Sep 14, 2026
f19a107
Merge remote-tracking branch 'origin/master' into feat/workspace-root…
davidfirst Sep 14, 2026
0d90dd0
fix(bit-map): read a component's own ignore file from disk, apply the…
davidfirst Sep 14, 2026
481443d
fix(bit-map): prefer the scope-qualified map key when checking the wo…
davidfirst Sep 14, 2026
c0cb2f7
fix(install): run the rewire codemod on the workspace-root component …
davidfirst Sep 15, 2026
401b4e4
revert: fix(install): run the rewire codemod on the workspace-root co…
davidfirst Sep 15, 2026
1208b92
feat(dependencies): skip dependency detection for the workspace-root …
davidfirst Sep 15, 2026
a6fd3a9
feat(workspace-root): new aspect, members record the root component t…
davidfirst Sep 15, 2026
e3fd2df
feat(workspace-root): snap a new or modified root along with its memb…
davidfirst Sep 16, 2026
f5f7916
fix(workspace-root): symlink guard for tracked roots, keep same-named…
davidfirst Sep 16, 2026
1d6d001
feat(workspace-root): bit clone, a workspace from its workspace-root …
davidfirst Sep 16, 2026
57708e5
fix(workspace-root): validate cloned dirs, drop the lanes dependency,…
davidfirst Sep 16, 2026
216f3ee
fix(workspace-root): name the scope when the cloned root is not on th…
davidfirst Sep 16, 2026
bcfb8b3
perf(importer): write a set of components to their own directories in…
davidfirst Sep 16, 2026
47c9c0b
fix(bit-map): version a component's scope in one field, so the first …
davidfirst Sep 16, 2026
fbbf9b4
fix(workspace-root): refuse a symlinked clone target, keep dotted dir…
davidfirst Sep 16, 2026
835765e
Merge remote-tracking branch 'origin/master' into feat/workspace-root…
davidfirst Sep 16, 2026
f51ca92
chore(deps): refresh the lockfile after the merge
davidfirst Sep 16, 2026
16122c6
fix(component-writer): refuse to move a component out of the workspac…
davidfirst Sep 16, 2026
65c74ee
fix(snapping): record the workspace root only on components the works…
davidfirst Sep 16, 2026
be52daf
fix(workspace-root): restore the caller's working directory after a s…
davidfirst Sep 16, 2026
dfda122
fix(watcher): ignore every file bit generates, not only package.json,…
davidfirst Sep 16, 2026
3578fa4
test(watcher): cover the ignore patterns a tracked workspace root dep…
davidfirst Sep 16, 2026
b46e253
fix(bit-map): apply nested ignore files deepest-last, and skip the sy…
davidfirst Sep 17, 2026
6735043
Merge branch 'master' into feat/workspace-root-component-nesting
davidfirst Sep 17, 2026
d0a9800
fix(snapping): keep a versions file from dictating the auto-added roo…
davidfirst Sep 17, 2026
424d8e8
test(tracker): cover the workspace-root add cases in a spec instead o…
davidfirst Sep 17, 2026
58c550a
fix(workspace-root): keep a clone and a root snapshot within their ow…
davidfirst Sep 17, 2026
a8013dc
fix(bit-map): compare root-dirs as directories, not as strings
davidfirst Sep 17, 2026
5366037
fix(bit-map): drop a deleted component from the versioned .bitmap
davidfirst Sep 17, 2026
47709cf
fix(workspace-root): render the components a clone could not fetch as…
davidfirst Sep 17, 2026
ef5271d
test(tracker): cover add-time ignore filtering in a spec instead of e2e
davidfirst Sep 17, 2026
38fcc79
fix(tracker): report a main file the component's ignore file excludes…
davidfirst Sep 17, 2026
3f5bb49
fix(component-writer): keep a root version from overwriting the sourc…
davidfirst Sep 17, 2026
3ddfdb6
fix(bit-map): let the root scan the dir of a removed or other-lane co…
davidfirst Sep 17, 2026
e38fc00
fix(component-writer): write an ordinary component's own .bitmap, onl…
davidfirst Sep 17, 2026
ff8fe8e
fix(tracker): keep the files bit generated when trackAllFiles is on, …
davidfirst Sep 17, 2026
6c3a6e6
test(e2e): drop an add case the getFilesByDir unit tests already cove…
davidfirst Sep 17, 2026
e18b450
fix(component-writer): keep skipping .bitmap for every component, no …
davidfirst Sep 17, 2026
7914f2f
fix(component-writer): apply the nested-component filter to the root …
davidfirst Sep 17, 2026
1a68acd
test(tracker): cover the root-and-nested batch add in a spec instead …
davidfirst Sep 17, 2026
4e0642c
fix(bit-map): drop a trailing slash from a nested root-dir, callers a…
davidfirst Sep 17, 2026
ec7bf2a
test(component-writer): pin that a tracked root component is written …
davidfirst Sep 17, 2026
e88a8a6
fix(component-writer): skip the live .bitmap in the root import prefl…
davidfirst Sep 17, 2026
482c1ec
fix(workspace-root): prefix the clone missing-components title with t…
davidfirst Sep 17, 2026
b6f8611
fix(tracker): apply the generated-files rule to an explicit main file…
davidfirst Sep 17, 2026
498fbd3
fix(bit-map): one lane-aware lookup for the component that owns the w…
davidfirst Sep 17, 2026
579f999
docs(watcher): say which generated configs the ignore patterns do not…
davidfirst Sep 17, 2026
44c56fa
fix(install): clear the install context in a finally, a caught failur…
davidfirst Sep 17, 2026
f51d709
fix(component-writer): check the generated config file for a symlink …
davidfirst Sep 17, 2026
eaffb84
test(e2e): drop a root-manifest assertion the clone flow already proves
davidfirst Sep 17, 2026
f34b8c1
Merge remote-tracking branch 'origin/master' into feat/workspace-root…
davidfirst Sep 17, 2026
66c26e7
test(e2e): cover cloning a lane with a version pinned on the root id
davidfirst Sep 17, 2026
de95944
fix(workspace-root): refuse an absolute rootDir that resolves inside …
davidfirst Sep 17, 2026
54d7da7
fix(workspace-root): resolve a symlinked ancestor before checking for…
davidfirst Sep 17, 2026
6705cd9
test: move the symlink preflight variants off the e2e tier
davidfirst Sep 17, 2026
363b7ab
fix(workspace-root): remove the directories a failed clone created on…
davidfirst Sep 17, 2026
ac10a7f
test(component-writer): cover that the batch write leaves the workspa…
davidfirst Sep 17, 2026
d6e24fa
Merge remote-tracking branch 'origin/master' into feat/workspace-root…
davidfirst Sep 17, 2026
f308627
fix(bit-map): keep .bitmap in the root file-set when the workspace ig…
davidfirst Sep 17, 2026
ca0cd2c
fix(bit-map): let the entry that owns a dir win the path index over a…
davidfirst Sep 17, 2026
2ae6395
fix(workspace-root): clear a root a component was snapped in when thi…
davidfirst Sep 17, 2026
4c7db9d
fix(workspace-root): reject a non-string rootDir with the clone error…
davidfirst Sep 17, 2026
783f894
fix(workspace-root): clear the former root pointer when a member beco…
davidfirst Sep 17, 2026
6ff664e
refactor(component-writer): resolve the per-component write path insi…
davidfirst Sep 18, 2026
eb1e095
test(workspace-root): cover a member imported alone into a workspace …
davidfirst Sep 18, 2026
8b59694
refactor(workspace-root): drop the clear helpers, aspect data is not …
davidfirst Sep 18, 2026
a34cb87
feat(tracker): require --root to track the workspace root with bit add
davidfirst Sep 18, 2026
4f2e1c3
docs(cli-reference): regenerate for the new bit add --root flag
davidfirst Sep 18, 2026
113ad10
fix(workspace-root): close three write and tracking holes found in re…
davidfirst Sep 18, 2026
49aefcb
fix(tracker): let the programmatic api track the workspace root witho…
davidfirst Sep 18, 2026
9e2becd
fix(snapping): keep a hidden-only merge from snapping the workspace root
davidfirst Sep 18, 2026
bb6f383
fix(workspace-root): refuse member directories a remote map should no…
davidfirst Sep 18, 2026
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
14 changes: 14 additions & 0 deletions .bitmap
Original file line number Diff line number Diff line change
Expand Up @@ -2319,6 +2319,20 @@
"mainFile": "index.ts",
"rootDir": "scopes/workspace/workspace-config-files"
},
"workspace-root": {
"name": "workspace-root",
"scope": "",
"version": "",
"defaultScope": "teambit.workspace",
"mainFile": "index.ts",
"rootDir": "scopes/workspace/workspace-root",
"config": {
"teambit.harmony/envs/core-aspect-env@2.1.1": {},
"teambit.envs/envs": {
"env": "teambit.harmony/envs/core-aspect-env"
}
}
},
"yarn": {
"name": "yarn",
"scope": "teambit.dependencies",
Expand Down
390 changes: 389 additions & 1 deletion components/legacy/bit-map/bit-map.spec.ts

Large diffs are not rendered by default.

257 changes: 236 additions & 21 deletions components/legacy/bit-map/bit-map.ts

Large diffs are not rendered by default.

203 changes: 203 additions & 0 deletions components/legacy/bit-map/component-map.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { expect } from 'chai';
import fs from 'fs-extra';
import os from 'os';
import path from 'path';
import { BIT_HIDDEN_DIR, BIT_WORKSPACE_TMP_DIRNAME, DOT_GIT_DIR } from '@teambit/legacy.constants';
import {
filterByOwnIgnoreFile,
filterByScanIgnorePatterns,
getFilesByDir,
getGitIgnoreHarmony,
getIgnoreListHarmony,
WORKSPACE_ROOT_DIR,
} from './component-map';

const createWorkspace = async (prefix: string, files: Record<string, string>): Promise<string> => {
const workspacePath = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
await Promise.all(
Object.entries(files).map(([file, content]) => fs.outputFile(path.join(workspacePath, file), content))
);
return workspacePath;
};

describe('getFilesByDir', function () {
this.timeout(0);
describe('trackAllFiles', () => {
let workspacePath: string;
before(async () => {
workspacePath = await createWorkspace('bit-track-all-files-', {
'.gitignore': 'dist/\n',
'comp/index.ts': '',
'comp/package.json': '',
'comp/tsconfig.json': '',
'comp/package-lock.json': '',
'comp/dist/index.js': '',
'comp/node_modules/dep/index.js': '',
});
});
after(() => fs.remove(workspacePath));

const filesOf = async (trackAllFiles: boolean): Promise<string[]> => {
const gitIgnore = await getGitIgnoreHarmony(workspacePath, undefined, trackAllFiles);
const files = await getFilesByDir('comp', workspacePath, gitIgnore, [], trackAllFiles);
return files.map((file) => file.relativePath).sort();
};

it('should skip the files bit generates, and the lockfiles, by default', async () => {
expect(await filesOf(false)).to.deep.equal(['index.ts']);
});
it('should track them with the flag on, while still honoring .gitignore and skipping node_modules', async () => {
expect(await filesOf(true)).to.deep.equal(['index.ts', 'package-lock.json', 'package.json', 'tsconfig.json']);
});
it('should keep the user ignore patterns and the hard exclusions with the flag on', async () => {
const ignoreList = await getIgnoreListHarmony(workspacePath, ['*.bak'], true);
expect(ignoreList).to.include('*.bak');
expect(ignoreList).to.include('**/node_modules/**');
expect(ignoreList).to.not.include('package.json');
});
it('should keep a lockfile pattern the user wrote, even though bit drops its own', async () => {
const otherWorkspace = await createWorkspace('bit-user-lockfile-', { '.gitignore': '**/yarn.lock\n' });
try {
const ignoreList = await getIgnoreListHarmony(otherWorkspace, undefined, true);
expect(ignoreList).to.include('**/yarn.lock');
expect(ignoreList).to.not.include('**/package-lock.json');
} finally {
await fs.remove(otherWorkspace);
}
});
});

describe('a workspace whose own ignore rules hide .bitmap', () => {
// some teams keep .bitmap out of git. the root component versions it all the same - it is the map
// a workspace is restored from, and what "bit clone" reads to know which components to import, so
// dropping it would make a clone come out empty with nothing to say why.
let workspacePath: string;
before(async () => {
workspacePath = await createWorkspace('bit-workspace-ignored-map-', {
'.bitmap': '',
'README.md': '',
'workspace.jsonc': '',
'.gitignore': '.bitmap\nbuild/\n',
'build/out.js': '',
});
});
after(() => fs.remove(workspacePath));

it('should keep .bitmap in the root file-set anyway', async () => {
const gitIgnore = await getGitIgnoreHarmony(workspacePath);
const files = await getFilesByDir(WORKSPACE_ROOT_DIR, workspacePath, gitIgnore, []);
expect(files.map((file) => file.relativePath)).to.include('.bitmap');
});
it('should still honor the rest of the rules, only that one file is spared', async () => {
const gitIgnore = await getGitIgnoreHarmony(workspacePath);
const files = await getFilesByDir(WORKSPACE_ROOT_DIR, workspacePath, gitIgnore, []);
expect(files.map((file) => file.relativePath)).to.not.include('build/out.js');
});
});

describe('scanning the workspace root', () => {
let workspacePath: string;
let outsidePath: string;
before(async () => {
workspacePath = await createWorkspace('bit-workspace-root-scan-', {
'README.md': '',
'.bitmap': '',
// the root's rules ignore every nested .bitignore file - a nested one still applies, as in git
'.gitignore': 'docs/*.txt\n**/.bitignore\n',
'workspace.jsonc': '',
'.github/ci.yml': '',
// a git worktree or submodule: .git is a pointer file, not a directory
[DOT_GIT_DIR]: 'gitdir: /elsewhere/.git/worktrees/this\n',
[`${BIT_HIDDEN_DIR}/objects/aa`]: '',
[`${BIT_WORKSPACE_TMP_DIRNAME}/x`]: '',
'node_modules/dep/index.js': '',
'packages/comp1/index.ts': '',
// a map at a nested component's root is never its file (that dir would be a nested workspace)
'packages/comp1/.bitmap': '',
// a nested component whose root-dir is glob syntax (a Next.js route), next to a dir that
// the unescaped pattern "app/[slug]" would match instead
'app/[slug]/page.ts': '',
'app/l/index.ts': '',
// a vendored repository and a nested bit workspace that no component claims
'vendor/lib/.git/HEAD': '',
'vendor/lib/index.js': '',
'vendor/lib/.bitignore': 'generated/\n',
'vendor/lib/generated/x.js': '',
'nested/.bit/objects/bb': '',
'nested/.bit.map.json': '',
'nested/.bitmap': '',
// an ignore file below the root applies to its directory, like git: unanchored patterns at any
// depth below it, anchored ones to it
// and a nested negation re-includes a file the root's rule excluded, evaluated in git's order,
// but never one of the files bit always excludes
'docs/.gitignore': 'build/\n/local.env\ntmp/\n!keep.txt\n!.env\n',
'docs/index.md': '',
'docs/notes.txt': '',
'docs/keep.txt': '',
'docs/.env': 'SECRET=1\n',
'docs/build/out.html': '',
'docs/local.env': '',
'docs/nested/local.env': '',
// "tmp/" ignores directories only. this is a file
'docs/tmp': '',
});
// a symbolic link at the root, to a directory outside the workspace. what it points to is not
// the workspace's source, so its files must never be tracked
outsidePath = await createWorkspace('bit-outside-the-workspace-', { 'secret.txt': 'SECRET' });
await fs.symlink(outsidePath, path.join(workspacePath, 'linked'));
});
after(() => Promise.all([fs.remove(workspacePath), fs.remove(outsidePath)]));

it("should apply a nested component's own .bitignore although the workspace rules hide that file from the list", async () => {
// the root .gitignore ignores every nested .bitignore, so it is not among the paths handed over
const filtered = await filterByOwnIgnoreFile('vendor/lib', workspacePath, ['index.js', 'generated/x.js']);
expect(filtered).to.deep.equal(['index.js']);
});
it('should drop from caller-resolved paths what a scan never yields', () => {
const dir = 'packages/comp1';
const paths = [
`${dir}/index.ts`,
`${dir}/.bitmap`,
`${dir}/${DOT_GIT_DIR}/HEAD`,
`${dir}/${BIT_HIDDEN_DIR}/objects/aa`,
`${dir}/${BIT_WORKSPACE_TMP_DIRNAME}/x`,
`${dir}/node_modules/dep/index.js`,
];
expect(filterByScanIgnorePatterns(dir, paths)).to.deep.equal([`${dir}/index.ts`]);
});

// bit runs with the workspace as its cwd. globby stats ignore patterns relative to the process
// cwd, so the `.git` pointer file is only exercised from inside the workspace.
const scanFromInsideTheWorkspace = async (dir: string, excludeDirs: string[]): Promise<string[]> => {
const originalCwd = process.cwd();
process.chdir(workspacePath);
try {
const gitIgnore = await getGitIgnoreHarmony(workspacePath);
const files = await getFilesByDir(dir, workspacePath, gitIgnore, excludeDirs);
return files.map((file) => file.relativePath).sort();
} finally {
process.chdir(originalCwd);
}
};

it('should own every file no other component claims, and skip the bit and git internals', async () => {
expect(await scanFromInsideTheWorkspace(WORKSPACE_ROOT_DIR, ['packages/comp1', 'app/[slug]'])).to.deep.equal([
'.bitmap',
'.github/ci.yml',
'.gitignore',
'README.md',
'app/l/index.ts',
'docs/.gitignore',
'docs/index.md',
'docs/keep.txt',
'docs/nested/local.env',
'docs/tmp',
'vendor/lib/index.js',
'workspace.jsonc',
]);
});
it('should scan a nested component in a git worktree, where .git is a file', async () => {
expect(await scanFromInsideTheWorkspace('packages/comp1', [])).to.deep.equal(['index.ts']);
});
});
});
Loading