feat(workspace): workspace-root component (rootDir ".") and the trackAllFiles flag - #10698
davidfirst wants to merge 43 commits into
Conversation
PR Summary by QodoAllow components to own the workspace root
AI Description
Diagram
High-Level Assessment
Files changed (7)
|
Code Review by Qodo
1. Redundant add flows slow test runs
|
|
Follow-up: the root component now tracks Tracking it verbatim does not converge — snapping rewrites every entry's So only the durable part of the map is versioned: Also fixed: adding a component inside the workspace root used to fail with "files already used by component", because the root had already claimed them. The root now yields to the more specific component and drops those files on its next scan. |
|
Code review by qodo was updated up to the latest commit f0ca113 |
|
Went through all 24 component issues one by one against the workspace-root component. The result was not "none of them are relevant" — testing changed the answer. I first ignored everything dependency-derived (18 issues). That made things worse: with So the list is narrowed to the three that misfire for a structural reason — the root component has no env toolchain, no compiler, and nothing imports it as a package:
Everything else is kept. The dependency-related issues never fire for a component whose files hold no imports, so ignoring them buys nothing and costs the guard when they do fire. Net effect: |
|
Code review by qodo was updated up to the latest commit d166385 |
…nd write paths the workspace-root component (rootDir ".") is a bag of the workspace's own config files. three things treated it as a regular source component: - env: it defaulted to the regular default env, giving it a compiler and a dependency policy it can never use. it now defaults to the empty env. an env set explicitly on it still wins. - install: its dir is the workspace root, so handing it to the package manager collided with the root project - pnpm resolved it to an empty "file:" spec and failed to build the lockfile, breaking "bit install" entirely. - write: importing it into another workspace wrote a .bitmap into a sub-directory, silently turning that dir into a broken nested workspace, and checking out an earlier version of it crashed on a non-BitError. the empty env removes the compiler-derived issue structurally, so the issue-ignore list added for this component is no longer needed and is reverted.
|
Follow-up on two questions raised in review: what happens when a workspace-root component is imported, and what env it should get. Env. It was defaulting to the regular default env, which hands a bag of config files a compiler and a dependency policy it can never satisfy. It now defaults to This turned out to be the better fix for the component-issues question. With no compiler, Import. Two real bugs, both reproduced:
Fixed by never writing Third bug found on the way: 17 e2e + 10 unit passing, lint clean. |
|
Code review by qodo was updated up to the latest commit 016b3c4 |
- remove/eject: rootDir "." was passed to RemovePath with recursive deletion, so removing the root component wiped the entire workspace - nested components, .bit, .bitmap and unrelated files. its files are the workspace's own, so untracking it now leaves them in place. - re-adding "bit add ." threw, since files were compared against a "./" prefix they never have. - a second component claiming the workspace root was accepted, then failed .bitmap's duplicate-rootDir validation on the next load. now rejected with a message naming the current owner. - "bit add ." skipped dotfiles and enumerated node_modules; it now uses the same ignore list as the rescan, so both agree on what the root component owns. - .bitTmp and the legacy .bit.map.json are excluded from the root file-set. - the .bitignore/.gitignore lookup resolved against the process cwd rather than the workspace. - the writer rejected a rootDir of "." whenever no .bitmap entry existed yet, which also blocked restoring a stashed root component. it now rejects only when a different component owns the root. - .bitmap normalization no longer clears "scope": unlike "version" it is stable after the first export, and clearing it collapsed components from other scopes onto the workspace default on restore.
|
Code review by qodo was updated up to the latest commit b2c0a9d |
…nto "." "bit import <root-component> --path ." crashed with an undefined path: "--path ." resolves to an empty relative path, which was stored as an empty rootDir. it is now normalized to ".", and the workspace root - which always holds .bit, .bitmap and workspace.jsonc - is no longer rejected as "not empty" for the component that owns it. this is the flow that restores a git-free workspace from its scope.
|
Code review by qodo was updated up to the latest commit 99c1fd8 |
|
Code review by qodo was updated up to the latest commit b49b410 |
…t-generated files bit drops package.json, a root-level tsconfig.json and lint configs, and the npm/yarn lockfiles from every component because it generates them. a workspace adopted from an existing monorepo owns those files, and without them a workspace restored from the scope can be neither installed nor built. with "trackAllFiles": true in teambit.workspace/workspace, only the git-ignored files and the hard exclusions (node_modules, .env, ...) are left out.
|
Code review by qodo was updated up to the latest commit d2b6186 |
…t, regardless of --override Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Code review by qodo was updated up to the latest commit 176b92c |
The root component has no entry point of its own, so "bit add ." no longer needs --main; workspace.jsonc stands in for it, and an explicit --main still wins. The bulk tracking API defaults it the same way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Code review by qodo was updated up to the latest commit 9bbf81f |
…ot merely harmless Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Code review by qodo was updated up to the latest commit 71d06d9 |
…root component It is never linked into node_modules, so the issue and its "run bit link" advice cannot apply to it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Code review by qodo was updated up to the latest commit 3eeed3c |
…-component-nesting # Conflicts: # components/legacy/bit-map/bit-map.spec.ts # components/legacy/consumer-component/consumer-component.ts
|
Code review by qodo was updated up to the latest commit f19a107 |
… scan exclusions to bulk-tracked paths, skip the root in the duplicate-package check
|
Code review by qodo was updated up to the latest commit 0d90dd0 |
…rkspace-root owner
|
Code review by qodo was updated up to the latest commit 481443d |
|
Code review by qodo was updated up to the latest commit c0cb2f7 |
…mponent as well The root component's files are no longer parsed for dependencies, so the relative-import issue that the codemod fixes cannot be raised for it.
| if (componentRootDir === WORKSPACE_ROOT_DIR) { | ||
| this.throwForNonWorkspaceRootComponent(component); | ||
| this.throwForSymlinksInTheWay(component); | ||
| } |
There was a problem hiding this comment.
1. Root checkouts overwrite outside files 🐞 Bug ⛨ Security
getWriteParamsOfOneComponent() invokes throwForSymlinksInTheWay() only when its initial path is ., before an existing bitmap entry changes the writer's destination to the workspace root. A checkout supplies no explicit path, so checking out a root file whose destination or ancestor is a symlink writes through that link to its target outside the workspace.
Agent Prompt
## Issue description
Workspace-root checkouts bypass symlink validation because the check runs before the existing bitmap entry changes the final write path to `.`.
## Fix Focus Areas
- scopes/component/component-writer/component-writer.main.runtime.ts[278-301]
- scopes/component/component-writer/component-writer.ts[78-90]
## Recommended Fix
Resolve the existing component map and final write destination before applying workspace-root validation, then run the symlink check whenever that final destination is `.`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const linkedComponents = components.filter( | ||
| (c) => consumer.bitMap.getComponentIfExist(c.id, { ignoreVersion: true })?.rootDir !== WORKSPACE_ROOT_DIR | ||
| ); |
There was a problem hiding this comment.
2. Removing the root drops a dependency 🐞 Bug ≡ Correctness
removeLocal() still passes the workspace-root component to removeComponentsFromDependencies(), although the new filtering excludes it only from node_modules cleanup. When the workspace declares an unrelated dependency with the root component's derived package name, removing the component deletes that dependency from the root package.json.
Agent Prompt
## Issue description
The workspace-root component is excluded from node_modules removal but still reaches package.json dependency cleanup, which can delete an unrelated dependency sharing its derived name.
## Fix Focus Areas
- scopes/component/remove/remove-components.ts[167-195]
## Recommended Fix
Partition workspace-root components before cleanup and exclude them from both `removeComponentsFromDependencies()` and `removeComponentsFromNodeModules()`, while retaining normal bitmap untracking.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (key === SCHEMA_FIELD || !entry || typeof entry !== 'object') return; | ||
| if (entry.version !== undefined) entry.version = ''; | ||
| delete entry.config; | ||
| }); |
There was a problem hiding this comment.
13. Persisted soft tags stay modified 🐞 Bug ≡ Correctness
normalizeBitmapContentForVersioning() removes versions and pending config but leaves each entry's transient nextVersion field in the versioned map. Persisting a soft tag captures that field in the root version and then clears it from the live bitmap, so status immediately detects different file contents.
Agent Prompt
## Issue description
The normalized version of `.bitmap` retains `nextVersion`, even though soft-tag persistence clears that transient field from the live map after loading component files.
## Fix Focus Areas
- components/legacy/bit-map/bit-map.ts[1137-1149]
- components/legacy/bit-map/bit-map.spec.ts[145-189]
## Recommended Fix
Delete `nextVersion` from every component entry during bitmap normalization and add a convergence test covering soft tag followed by persist and status.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const filteredMatches = matchesNotIgnored.filter( | ||
| (match) => | ||
| keptByOwnIgnoreFile.has(relativeToComponent(match)) && | ||
| (this.consumer.config.trackAllFiles || !generatedAtRoot.has(match)) | ||
| ); |
There was a problem hiding this comment.
14. Some root main choices break loading 🐞 Bug ≡ Correctness
addOneComponent() removes root-only generated files before _addMainFileToFiles(), but that method can append an explicitly selected file without applying the same exclusion. Running `bit add . --main tsconfig.json without trackAllFiles` therefore succeeds initially, while the next bitmap rescan drops the main file and leaves the root component invalid.
Agent Prompt
## Issue description
An explicit workspace-root main file can be reintroduced after the add-time scan excluded it, producing a bitmap that fails after the next rescan.
## Fix Focus Areas
- scopes/component/tracker/add-components.ts[572-607]
- scopes/component/tracker/determine-main-file.spec.ts[19-33]
## Recommended Fix
Validate an explicit main file against all scan exclusions, including root-only generated files and component-local ignore rules, before appending it; reject excluded choices with `ExcludedMainFile`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Code review by qodo was updated up to the latest commit 1208b92 |
…hey were snapped in
| it('should allow re-adding the same component', () => { | ||
| helper.fs.outputFile('extra.md', 'extra\n'); | ||
| expect(() => helper.command.addComponent('.', { i: 'ws-root' })).to.not.throw(); |
There was a problem hiding this comment.
1. Redundant add flows slow test runs 📘 Rule violation ➹ Performance
add-harmony.e2e.ts invokes the CLI to verify main-file selection and same-component re-add behavior already covered by colocated unit specifications. These single-workspace variants require neither a remote scope nor a cross-command restore flow, so every E2E run executes unnecessary workspace commands.
Agent Prompt
## Issue description
The added E2E cases exercise main-file selection and repeated component addition that colocated unit specifications already cover, increasing E2E runtime without testing a cross-command or remote-scope flow.
## Fix Focus Areas
- e2e/harmony/add-harmony.e2e.ts[188-216]
- scopes/component/tracker/determine-main-file.spec.ts[23-31]
- components/legacy/bit-map/bit-map.spec.ts[199-216]
## Recommended Fix
Remove the redundant E2E variants and retain or extend the existing unit specifications for explicit main-file selection, preserving an existing main file during re-tracking, and rejecting or accepting duplicate root ownership.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const patternsPerDir = await Promise.all( | ||
| Array.from(ignoreFileByDir, async ([fileDir, name]) => { | ||
| const absoluteDir = path.join(consumerPath, fileDir); | ||
| const patterns = name === BIT_IGNORE ? await getBitIgnoreFile(absoluteDir) : await getGitIgnoreFile(absoluteDir); |
There was a problem hiding this comment.
2. Scanner formatting fails the gate 📘 Rule violation ⚙ Maintainability
getNestedIgnorePatterns() and its new scanner tests leave expressions beyond the configured 120-column layout instead of Prettier's wrapped form. When npm run prettier:check scans the changed component files, Prettier lists them as different and the formatting gate exits unsuccessfully.
Agent Prompt
## Issue description
Changed scanner source and test expressions do not match the repository's configured Prettier layout, causing the canonical formatting check to fail.
## Fix Focus Areas
- components/legacy/bit-map/component-map.ts[127-193]
- components/legacy/bit-map/component-map.spec.ts[15-156]
## Recommended Fix
Run the repository Prettier formatter on both files and commit its multiline wrapping, then verify them with `npm run prettier:check`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const filePath = path.join(compDirAbs, file.relativePath); | ||
| return SourceFile.load(filePath, compDirAbs, this.path, {}); | ||
| const sourceFile = SourceFile.load(filePath, compDirAbs, this.path, {}); | ||
| sourceFile.contents = fileContentsForVersioning(bitMapEntry, file.relativePath, sourceFile.contents); |
There was a problem hiding this comment.
15. New workspace files evade quick status 🐞 Bug ≡ Correctness
Workspace.getFilesModification() builds CompFiles from cached bitMapEntry.files and only normalizes the contents of files already in that list, without calling trackDirectoryChanges(). When a file is added to a workspace-root component after the workspace is loaded, status --quick and other callers of this method omit it until a full component load happens to rescan the root.
Agent Prompt
Issue description
`Workspace.getFilesModification()` compares the cached bitmap file list directly. A workspace-root component owns newly added unclaimed files only after its directory is rescanned, so quick status can report it clean while it has new root files.
Fix Focus Areas
- scopes/workspace/workspace/workspace.ts[768-776]
- components/legacy/bit-map/bit-map.ts[260-269]
Recommended Fix
Before deriving `compDir` and mapping `bitMapEntry.files`, detect `bitMapEntry.rootDir === WORKSPACE_ROOT_DIR` and call `await this.consumer.bitMap.trackDirectoryChanges(bitMapEntry)`. This refreshes the root component's owned file set and path index before `CompFiles` compares workspace files with the model, while preserving the existing behavior for ordinary components.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Code review by qodo was updated up to the latest commit a6fd3a9 |
Context: the Bit side of pnpm/rfcs#33, Bit version control for pnpm workspaces: each pnpm project is a component and the unclaimed files belong to a root component. This PR lands that root-component model in bit; the adoption command and the pnpm-specific pieces follow, based on #10675.
Lets a single component own the workspace root (
rootDir: "."), and adds a workspace flag that tracks the files bit normally treats as generated. Together they make a git-free workspace restorable from its scope: the root component carries the repository-level files and.bitmap, and the flag keepspackage.jsonand friends.Workspace-root component
On the name: "root component" already means the dependency-resolver's
rootComponents(envs and apps installed as roots undernode_modules/.bit_roots), and "workspace component" is every component loaded from a workspace (WorkspaceComponent). "Workspace-root component" is whatrootDir: "."says, clashes with neither, and pairs with "nested components" for the ones inside it. Code uses theWORKSPACE_ROOT_DIRconstant and theworkspaceRootprefix.rootDir: "."is valid and is the only root-dir allowed to contain other components. Its file-set is everything under the root minus the nested components' root-dirs, re-scanned like any other component, so files added later are picked up..bit/,.git/andnode_modulesare never claimed..bitmap, with versions normalized on load so it converges after a snap. The writer never writes.bitmapback, so an imported root cannot create a phantom nested workspace.bit add .tracks it withteambit.harmony/empty-envas explicit config (so env resolution and the dependency policy agree), and it is excluded from install and link. Its files are not parsed for dependencies either: nothing installs, links or builds the root, and repo scripts may require anything, so detection would only produce blocking issues with no consumer for the result. Its main file defaults toworkspace.jsonc, the root has no entry point of its own;--mainstill overrides.bit removeandbit ejectdo not delete the workspace. Re-adding it is a no-op; a second root component is rejected at add time.teambit.workspace/workspace-rootowns the concept. On snap, every member of the workspace records the root it was snapped in, at the root's version after that snap, as aspect data:{ "root": "scope/root@version" }. Data, not config, so it never makes a member modified and the root moving on does not touch them. It tells a CI or a clone which root files (lockfile, tsconfig, scripts) a version was made with, andbit showprints it as "workspace root". The root itself records nothing.bit import <root> --path .restores it onto an empty workspace..without--overrideis accepted only in a fresh workspace (nothing else tracked), which is the restore flow; an established workspace gets the usual conflict error listing the root files that would be overwritten.trackAllFiles"trackAllFiles": trueunderteambit.workspace/workspacestops bit from droppingpackage.json, a root-leveltsconfig.jsonand lint configs, and the npm/yarn lockfiles. Only git-ignored files and the hard exclusions stay out. Meant for workspaces adopted from an existing monorepo, where those files are the source of truth. Import writes the model's files regardless, so a component with a trackedpackage.jsonshows as modified in a workspace without the flag.Tests
bit-map.spec.ts(nesting rules,getNestedRootDirs,.bitmapnormalization),component-map.spec.ts(ignore logic with and without the flag) anddetermine-main-file.spec.ts(the root's main-file default),workspace-root-data.spec.ts(the snapped-in root record).add-harmony.e2e.tscovers root tracking,.bitmapconvergence, remove, re-add, checkout, import into another workspace and onto., env defaults, and adopt → export → restore with the flag.