@teambit/tracker 1.0.1173 → 1.0.1175

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.addMultipleFromResolvedTrackData = addMultipleFromResolvedTrackData;
7
+ exports.configForWorkspaceRoot = configForWorkspaceRoot;
7
8
  exports.default = void 0;
8
9
  function _arrayDifference() {
9
10
  const data = _interopRequireDefault(require("array-difference"));
@@ -175,6 +176,8 @@ function _determineMainFile() {
175
176
  }
176
177
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
177
178
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
179
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
180
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
178
181
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
179
182
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
180
183
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
@@ -183,7 +186,6 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
183
186
 
184
187
  const REGEX_DSL_PATTERN = /{([^}]+)}/g;
185
188
  class AddComponents {
186
- // only bit-add (not bit-create/new) should handle out-of-sync scenario
187
189
  constructor(context, addProps) {
188
190
  _defineProperty(this, "workspace", void 0);
189
191
  _defineProperty(this, "consumer", void 0);
@@ -204,6 +206,8 @@ class AddComponents {
204
206
  // helpful for out-of-sync
205
207
  _defineProperty(this, "config", void 0);
206
208
  _defineProperty(this, "shouldHandleOutOfSync", void 0);
209
+ // only bit-add (not bit-create/new) should handle out-of-sync scenario
210
+ _defineProperty(this, "root", void 0);
207
211
  this.workspace = context.workspace;
208
212
  this.consumer = context.workspace.consumer;
209
213
  this.bitMap = this.consumer.bitMap;
@@ -222,6 +226,40 @@ class AddComponents {
222
226
  this.defaultScope = addProps.defaultScope;
223
227
  this.config = addProps.config;
224
228
  this.shouldHandleOutOfSync = addProps.shouldHandleOutOfSync;
229
+ this.root = addProps.root;
230
+ }
231
+
232
+ /**
233
+ * tracking the workspace root turns the workspace itself into a component: it owns every file no
234
+ * other component claims, `.bitmap` included, and every other component becomes a member of it.
235
+ * "bit add ." is one keystroke away from "git add .", which means something else entirely, so the
236
+ * intent is spelled out rather than inferred from the path.
237
+ *
238
+ * only the workspace root needs the flag, and it is the only thing the flag does: "bit add ." from
239
+ * a sub-directory names that directory, not the root, and is tracked like any other path.
240
+ *
241
+ * a workspace that already has a root does not ask again. what the flag guards against is creating
242
+ * one by accident, and there is none to create here - the add either refreshes the root that exists
243
+ * or names a second owner for ".", which has its own message saying who holds it.
244
+ */
245
+ /**
246
+ * the paths reach here as the user typed them - relative to the cwd, or absolute - so the workspace
247
+ * root arrives as ".", as "", or as the workspace path itself, depending on where the command ran.
248
+ */
249
+ isWorkspaceRootPath(onePath) {
250
+ return ((0, _legacy5().pathNormalizeToLinux)(this.consumer.getPathRelativeToConsumer(onePath)) || _legacy3().WORKSPACE_ROOT_DIR) === _legacy3().WORKSPACE_ROOT_DIR;
251
+ }
252
+ throwForWorkspaceRootFlagMismatch(resolvedPaths) {
253
+ const tracksRoot = resolvedPaths.some(onePath => this.isWorkspaceRootPath(onePath));
254
+ if (tracksRoot && !this.root && !this.bitMap.getWorkspaceRootMap()) {
255
+ throw new (_bitError().BitError)(`unable to track the workspace root without the --root flag.
256
+ it makes the workspace itself a component - it owns every file no other component claims, .bitmap included, and every other component becomes a member of it.
257
+ if that is what you want, run "bit add . --root". to track a component, pass its directory, e.g. "bit add my-component"`);
258
+ }
259
+ if (!tracksRoot && this.root) {
260
+ throw new (_bitError().BitError)(`the --root flag tracks the workspace root, but none of the given paths is the workspace root.
261
+ run "bit add . --root" from the workspace root, or drop the flag to track the given paths as ordinary components`);
262
+ }
225
263
  }
226
264
  async add() {
227
265
  this.ignoreList = await this.getIgnoreList();
@@ -246,6 +284,7 @@ class AddComponents {
246
284
  throw new (_addingIndividualFiles().AddingIndividualFiles)(compPath);
247
285
  }
248
286
  });
287
+ this.throwForWorkspaceRootFlagMismatch(Object.keys(componentPathsStats));
249
288
  if (Object.keys(componentPathsStats).length > 1 && this.id) {
250
289
  throw new (_bitError().BitError)(`the --id flag (${this.id}) is used for a single component only, however, got ${this.componentPaths.length} paths`);
251
290
  }
@@ -340,17 +379,32 @@ class AddComponents {
340
379
  const foundComponentFromBitMap = this.bitMap.getComponentIfExist(component.componentId, {
341
380
  ignoreVersion: true
342
381
  });
382
+ // the workspace-root component owns every file no other component claims, so it "owns" a file
383
+ // only until a more specific component is added for it: the component being added wins, and the
384
+ // root subtracts the new root-dir from its own file-set on the next scan. the one file it cannot
385
+ // give away is its main file - without it, it fails to load from the next scan on.
386
+ const workspaceRootMap = this.bitMap.getWorkspaceRootMap();
387
+ const isWorkspaceRoot = component.trackDir === _legacy3().WORKSPACE_ROOT_DIR;
388
+ if (workspaceRootMap && !isWorkspaceRoot && !parsedBitId.isEqualWithoutVersion(workspaceRootMap.id)) {
389
+ throwForTakingWorkspaceRootMainFile(workspaceRootMap, parsedBitId, (0, _legacy5().pathNormalizeToLinux)(component.trackDir));
390
+ }
343
391
  const componentFilesP = files.map(async file => {
344
392
  // $FlowFixMe null is removed later on
345
393
  const filePath = path().join(consumerPath, file.relativePath);
394
+ // the workspace map carries the auto-generated banner, but the root component tracks it on
395
+ // purpose (see isWorkspaceMapFile), and the rescan keeps it. the add-time file-set does too.
346
396
  const isAutoGenerated = await isAutoGeneratedFile(filePath);
347
- if (isAutoGenerated) {
397
+ // trackAllFiles keeps the files bit generates, and the rescan never looks at the banner at all
398
+ // (see getFilesByDir) - so dropping them here would leave the add-time file-set short of it.
399
+ const keepAutoGenerated = this.consumer.config.trackAllFiles || isWorkspaceRoot && (0, _legacy3().isWorkspaceMapFile)(file.relativePath);
400
+ if (isAutoGenerated && !keepAutoGenerated) {
348
401
  return null;
349
402
  }
350
403
  const caseSensitive = false;
351
404
  const existingIdOfFile = this.bitMap.getComponentIdByPath(file.relativePath, caseSensitive);
352
405
  const idOfFileIsDifferent = existingIdOfFile && !existingIdOfFile.isEqual(parsedBitId);
353
- if (idOfFileIsDifferent) {
406
+ const ownedByWorkspaceRoot = Boolean(workspaceRootMap && existingIdOfFile?.isEqualWithoutVersion(workspaceRootMap.id));
407
+ if (idOfFileIsDifferent && !ownedByWorkspaceRoot) {
354
408
  // not imported component file but exists in bitmap
355
409
  // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
356
410
  if (this.warnings.alreadyUsed[existingIdOfFile]) {
@@ -389,7 +443,9 @@ class AddComponents {
389
443
  return foundComponentFromBitMap;
390
444
  }
391
445
  }
392
- if (!this.override && foundComponentFromBitMap) {
446
+ // the root's files are whatever the scan says. a component nested since the previous add owns the
447
+ // files the previous entry listed for the root, so they are not merged back.
448
+ if (!this.override && foundComponentFromBitMap && !isWorkspaceRoot) {
393
449
  this._updateFilesWithCurrentLetterCases(foundComponentFromBitMap, componentFiles);
394
450
  component.files = this._mergeFilesWithExistingComponentMapFiles(componentFiles, foundComponentFromBitMap.files);
395
451
  } else {
@@ -404,7 +460,8 @@ class AddComponents {
404
460
  if (this.trackDirFeature) throw new Error('track dir should not calculate the rootDir');
405
461
  if (foundComponentFromBitMap) return foundComponentFromBitMap.rootDir;
406
462
  if (!trackDir) throw new Error(`addOrUpdateComponentInBitMap expect to have trackDir for non-legacy workspace`);
407
- const fileNotInsideTrackDir = componentFiles.find(file => !(0, _legacy5().pathNormalizeToLinux)(file.relativePath).startsWith(`${(0, _legacy5().pathNormalizeToLinux)(trackDir)}/`));
463
+ // every file in the workspace is inside the workspace root, so there is nothing to check.
464
+ const fileNotInsideTrackDir = trackDir === _legacy3().WORKSPACE_ROOT_DIR ? undefined : componentFiles.find(file => !(0, _legacy5().pathNormalizeToLinux)(file.relativePath).startsWith(`${(0, _legacy5().pathNormalizeToLinux)(trackDir)}/`));
408
465
  if (fileNotInsideTrackDir) {
409
466
  // we check for this error before. however, it's possible that a user have one trackDir
410
467
  // and another dir for the tests.
@@ -429,7 +486,7 @@ class AddComponents {
429
486
  componentId: new (_componentId().ComponentID)(componentId._legacy, defaultScope),
430
487
  files: component.files,
431
488
  defaultScope,
432
- config: this.config,
489
+ config: rootDir === _legacy3().WORKSPACE_ROOT_DIR ? configForWorkspaceRoot(foundComponentFromBitMap?.config, this.config) : this.config,
433
490
  mainFile,
434
491
  // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
435
492
  override: this.override
@@ -450,6 +507,11 @@ class AddComponents {
450
507
  _updateFilesAccordingToExistingRootDir(foundComponentFromBitMap, componentFiles, component) {
451
508
  const existingRootDir = foundComponentFromBitMap.rootDir;
452
509
  if (!existingRootDir) return; // nothing to do.
510
+ // every file in the workspace is inside the workspace root, and its paths are already relative
511
+ // to it, so there is nothing to check and nothing to rewrite. without this, re-running
512
+ // "bit add ." would compare "README.md" against a "./" prefix, decide the files are outside the
513
+ // root, and throw.
514
+ if (existingRootDir === _legacy3().WORKSPACE_ROOT_DIR) return;
453
515
  const areFilesInsideExistingRootDir = componentFiles.every(file => (0, _legacy5().pathNormalizeToLinux)(file.relativePath).startsWith(`${existingRootDir}/`));
454
516
  if (areFilesInsideExistingRootDir) {
455
517
  _legacy3().ComponentMap.changeFilesPathAccordingToItsRootDir(existingRootDir, componentFiles);
@@ -588,21 +650,49 @@ you can add the directory these files are located at and it'll change the root d
588
650
  * e.g. bar/foo.js, the id would be bar/foo.
589
651
  * in case bitmap has already the same id, the complete id is taken from bitmap (see _getIdAccordingToExistingComponent)
590
652
  */
591
- async addOneComponent(componentPath) {
653
+ async addOneComponent(componentPath, batchRootDirs = []) {
592
654
  let finalBitId; // final id to use for bitmap file
593
655
  let idFromPath;
594
656
  if (this.id) {
595
657
  finalBitId = this._getIdAccordingToExistingComponent(this.id);
596
658
  }
597
- const relativeComponentPath = this.consumer.getPathRelativeToConsumer(componentPath);
659
+ // when the tracked dir is the workspace root itself, the relative path is empty. normalize it
660
+ // to "." so it is a real rootDir rather than a falsy one.
661
+ const relativeComponentPath = this.consumer.getPathRelativeToConsumer(componentPath) || _legacy3().WORKSPACE_ROOT_DIR;
598
662
  this._throwForOutsideConsumer(relativeComponentPath);
599
- throwForExistingParentDir(this.bitMap, relativeComponentPath);
663
+ // the component this add is for: the one the user named, tracked already or not, or else the
664
+ // one already tracking this dir. for the workspace root, this is what tells a re-add from a
665
+ // second owner.
666
+ const idOfTrackDir = this._getIdAccordingToTrackDir(relativeComponentPath);
667
+ const idToAdd = this.id ? finalBitId : idOfTrackDir;
668
+ throwForExistingParentDir(this.bitMap, relativeComponentPath, idToAdd || undefined);
669
+ // files of components nested inside this dir belong to them, not to the component being added -
670
+ // whether they are tracked already or added by the same command.
671
+ const nestedRootDirs = (0, _lodash().uniq)([...this.bitMap.getNestedRootDirs(relativeComponentPath), ...batchRootDirs.filter(other => other !== relativeComponentPath && (relativeComponentPath === _legacy3().WORKSPACE_ROOT_DIR || other.startsWith(`${relativeComponentPath}/`)))]);
600
672
  const matches = await (0, _glob().glob)((0, _legacy5().pathNormalizeToLinux)(path().join(relativeComponentPath, '**')), {
601
673
  cwd: this.consumer.getPath(),
602
- nodir: true
674
+ nodir: true,
675
+ // dotfiles are component files like any other (the workspace root is full of them: .gitignore,
676
+ // .github/**). the rescan, getFilesByDir(), scans with dot: true, and the add-time file-set has to
677
+ // agree with it rather than be corrected by the next command.
678
+ dot: true,
679
+ // the same exclusions the rescan applies, see getFilesByDir().
680
+ ignore: (0, _legacy3().getScanIgnorePatterns)(relativeComponentPath, nestedRootDirs)
603
681
  });
604
682
  if (!matches.length) throw new (_exceptions().EmptyDirectory)(componentPath);
605
- const filteredMatches = this.gitIgnore.filter(matches);
683
+
684
+ // the config files "bit ws-config write" generates are not source - the same rule the rescan
685
+ // applies (see getFilesByDir), so the add-time file-set matches the next rescan.
686
+ const generatedAtRoot = new Set(_legacy2().IGNORE_ROOT_ONLY_LIST.map(file => (0, _legacy5().pathNormalizeToLinux)(path().join(relativeComponentPath, file))));
687
+ const matchesNotIgnored = await (0, _legacy3().filterByIgnoreFiles)(relativeComponentPath, this.consumer.getPath(), this.gitIgnore, matches.map(_legacy5().pathNormalizeToLinux), this.consumer.config.trackAllFiles);
688
+ // and by the component's own ignore file, the rule the rescan applies (see getFilesByDir) - it
689
+ // takes paths relative to the component, the matches are relative to the workspace.
690
+ const relativeToComponent = match => path().posix.relative(relativeComponentPath, match);
691
+ const keptByOwnIgnoreFile = new Set(await (0, _legacy3().filterByOwnIgnoreFile)(relativeComponentPath, this.consumer.getPath(), matchesNotIgnored.map(relativeToComponent)));
692
+ // the rules the rescan applies too (see getFilesByDir), so the add-time file-set matches it. the
693
+ // main file is checked against the same predicate below - it is put back after this filtering.
694
+ const isTrackable = match => keptByOwnIgnoreFile.has(relativeToComponent(match)) && (this.consumer.config.trackAllFiles || !generatedAtRoot.has(match));
695
+ const filteredMatches = matchesNotIgnored.filter(isTrackable);
606
696
  if (!filteredMatches.length) {
607
697
  throw new (_exceptions().NoFiles)(matches);
608
698
  }
@@ -614,10 +704,29 @@ you can add the directory these files are located at and it'll change the root d
614
704
  };
615
705
  });
616
706
  const resolvedMainFile = this._addMainFileToFiles(filteredMatchedFiles);
707
+ // that puts the main file back into the list after the filtering above, and checks it against the
708
+ // workspace ignore rules only. without applying the rest of them here, the add fails further down
709
+ // on a main file the rescan already dropped, saying it was removed - the config files bit treats
710
+ // as generated (tsconfig.json and friends) reach it that way, as does a component ignore file.
711
+ if (resolvedMainFile) {
712
+ const mainNormalized = (0, _legacy5().pathNormalizeToLinux)(resolvedMainFile);
713
+ // it is in the file-set only if it was found on disk. a missing one is reported further down,
714
+ // where the error says so rather than blaming an ignore rule.
715
+ const inFileSet = filteredMatchedFiles.some(file => file.relativePath === mainNormalized);
716
+ // some paths the scan never yields at all - bit's own dirs, a nested workspace map, the
717
+ // root-dir of a component nested in this one - so they never reach `matches` and the ignore
718
+ // rules below have nothing to compare them against. either way an explicit main file is put
719
+ // into the file-set without going through the scan, and the next rescan drops it again: the
720
+ // component then fails to load, saying the main file was removed.
721
+ const excludedFromScan = !(0, _legacy3().filterByScanIgnorePatterns)(relativeComponentPath, [mainNormalized], nestedRootDirs).length;
722
+ const excludedByIgnoreRules = matchesNotIgnored.includes(mainNormalized) && !isTrackable(mainNormalized);
723
+ if (inFileSet && excludedFromScan || excludedByIgnoreRules) {
724
+ throw new (_exceptions().ExcludedMainFile)(relativeToComponent(mainNormalized));
725
+ }
726
+ }
617
727
  const absoluteComponentPath = (0, _legacy5().pathNormalizeToLinux)(path().resolve(componentPath));
618
728
  const splitPath = absoluteComponentPath.split('/');
619
729
  const lastDir = splitPath[splitPath.length - 1];
620
- const idOfTrackDir = this._getIdAccordingToTrackDir(componentPath);
621
730
  if (!finalBitId) {
622
731
  if (this.id) {
623
732
  const bitId = _legacyBitId().BitId.parse(this.id, false);
@@ -651,7 +760,7 @@ you can add the directory these files are located at and it'll change the root d
651
760
  }
652
761
  async getIgnoreList() {
653
762
  const consumerPath = this.consumer.getPath();
654
- return (0, _legacy3().getIgnoreListHarmony)(consumerPath, this.consumer.config.ignoredFiles);
763
+ return (0, _legacy3().getIgnoreListHarmony)(consumerPath, this.consumer.config.ignoredFiles, this.consumer.config.trackAllFiles);
655
764
  }
656
765
  async linkComponents(ids) {
657
766
  if (this.trackDirFeature) {
@@ -678,8 +787,13 @@ you can add the directory these files are located at and it'll change the root d
678
787
  _removeDirectoriesWhenTheirFilesAreAdded(componentPathsStats) {
679
788
  const allPaths = Object.keys(componentPathsStats);
680
789
  allPaths.forEach(componentPath => {
681
- const foundDir = allPaths.find(p => p === path().dirname(componentPath));
682
- if (foundDir && componentPathsStats[foundDir]) {
790
+ // the dirname of "." is "." itself - the workspace root is not a wildcard expansion of itself.
791
+ const foundDir = allPaths.find(p => p !== componentPath && p === path().dirname(componentPath));
792
+ // nor of what sits directly below it. the workspace root contains every other component by
793
+ // design, so a component inside it is no reason to drop it: it was asked for by name, and the
794
+ // --root flag says so in as many words (see throwForWorkspaceRootFlagMismatch). without this,
795
+ // "bit add . comp --root" tracks comp alone and reports success, the root never created.
796
+ if (foundDir && componentPathsStats[foundDir] && !this.isWorkspaceRootPath(foundDir)) {
683
797
  _legacy4().logger.debug(`add-components._removeDirectoriesWhenTheirFilesAreAdded, ignoring ${foundDir}`);
684
798
  delete componentPathsStats[foundDir];
685
799
  }
@@ -721,9 +835,10 @@ you can add the directory these files are located at and it'll change the root d
721
835
  }));
722
836
  }
723
837
  async _tryAddingMultiple(componentPathsStats) {
838
+ const batchRootDirs = Object.keys(componentPathsStats).map(onePath => (0, _legacy5().pathNormalizeToLinux)(this.consumer.getPathRelativeToConsumer(onePath)) || _legacy3().WORKSPACE_ROOT_DIR);
724
839
  const addedP = Object.keys(componentPathsStats).map(async onePath => {
725
840
  try {
726
- const addedComponent = await this.addOneComponent(onePath);
841
+ const addedComponent = await this.addOneComponent(onePath, batchRootDirs);
727
842
  return addedComponent;
728
843
  } catch (err) {
729
844
  if (!(err instanceof _exceptions().EmptyDirectory)) throw err;
@@ -741,13 +856,28 @@ you can add the directory these files are located at and it'll change the root d
741
856
  }
742
857
  }
743
858
  exports.default = AddComponents;
744
- function throwForExistingParentDir(bitMap, relativeToConsumerPath) {
859
+ function throwForExistingParentDir(bitMap, relativeToConsumerPath, addedId) {
860
+ if (relativeToConsumerPath === _legacy3().WORKSPACE_ROOT_DIR) {
861
+ // only one component can own the workspace root. rejected before the (expensive) scan of the
862
+ // whole workspace, with a message that says which component already owns it. re-adding the same
863
+ // component is fine. the root component contains every other component by design, so there is no
864
+ // parent-dir conflict to check for it.
865
+ const currentOwner = bitMap.getComponentIdByRootPath(_legacy3().WORKSPACE_ROOT_DIR);
866
+ if (currentOwner && !addedId?.isEqual(currentOwner, {
867
+ ignoreVersion: true
868
+ })) {
869
+ throw new (_bitError().BitError)(`unable to track the workspace root, it is already tracked by "${currentOwner.toStringWithoutVersion()}"`);
870
+ }
871
+ return;
872
+ }
745
873
  const isParentDir = parent => {
746
874
  const relative = path().relative(parent, relativeToConsumerPath);
747
875
  return relative && !relative.startsWith('..') && !path().isAbsolute(relative);
748
876
  };
749
877
  bitMap.components.forEach(componentMap => {
750
- if (!componentMap.rootDir) return;
878
+ // the workspace-root component contains every other component by design, and subtracts their
879
+ // root-dirs from its own file-set.
880
+ if (!componentMap.rootDir || componentMap.rootDir === _legacy3().WORKSPACE_ROOT_DIR) return;
751
881
  if (isParentDir(componentMap.rootDir)) {
752
882
  throw new (_parentDirTracked().ParentDirTracked)(componentMap.rootDir, componentMap.id.toStringWithoutVersion(), relativeToConsumerPath);
753
883
  }
@@ -809,46 +939,135 @@ async function isAutoGeneratedFile(filePath) {
809
939
  const line = await (0, _firstline().default)(filePath);
810
940
  return line.includes(_legacy2().AUTO_GENERATED_STAMP);
811
941
  }
942
+
943
+ /**
944
+ * the env the workspace-root component is tracked with. hardcoded, the same way the envs aspect
945
+ * names its default env - the tracker does not depend on the env aspects.
946
+ */
947
+ const WORKSPACE_ROOT_ENV = 'teambit.harmony/empty-env';
948
+
949
+ /**
950
+ * the workspace-root component (rootDir ".") is a bag of config files, not a source component:
951
+ * nothing compiles it, nothing tests it, and nothing imports it as a package. the regular default
952
+ * env would give it a compiler and a dependency policy it can never satisfy, so it is tracked with
953
+ * the empty env - as explicit config, so every path that resolves an env or a dependency policy
954
+ * sees the same answer. an env configured by the caller, or set later with "bit env set", wins.
955
+ */
956
+ function configForWorkspaceRoot(existingConfig, addedConfig) {
957
+ const configuredEnv = addedConfig?.[_legacy2().Extensions.envs] ?? existingConfig?.[_legacy2().Extensions.envs];
958
+ if (configuredEnv) return _objectSpread(_objectSpread({}, existingConfig), addedConfig);
959
+ // the same two entries "bit env set" writes: the env aspect itself, and the env selection
960
+ return _objectSpread(_objectSpread(_objectSpread({}, existingConfig), addedConfig), {}, {
961
+ [WORKSPACE_ROOT_ENV]: {},
962
+ [_legacy2().Extensions.envs]: {
963
+ env: WORKSPACE_ROOT_ENV
964
+ }
965
+ });
966
+ }
812
967
  async function addMultipleFromResolvedTrackData(workspace, trackData) {
813
968
  const bitMap = workspace.consumer.bitMap;
814
- const ignoreList = await (0, _legacy3().getIgnoreListHarmony)(workspace.path, workspace.consumer.config.ignoredFiles);
969
+ const {
970
+ ignoredFiles,
971
+ trackAllFiles
972
+ } = workspace.consumer.config;
973
+ const ignoreList = await (0, _legacy3().getIgnoreListHarmony)(workspace.path, ignoredFiles, trackAllFiles);
815
974
  const gitIgnore = (0, _ignore().default)().add(ignoreList);
816
- const componentMaps = trackData.map(data => {
975
+ // normalized once, the way the map stores it, so "./" is the workspace root here as well as there
976
+ const normalizeRootDir = rootDir => (0, _legacy5().pathNormalizeToLinux)(path().normalize(rootDir));
977
+ const batchRootDirs = trackData.map(data => normalizeRootDir(data.rootDir));
978
+ const componentMaps = [];
979
+ for (const data of trackData) {
817
980
  const {
818
- rootDir,
819
981
  files,
820
982
  componentName,
821
983
  defaultScope,
822
- mainFile,
823
984
  config
824
985
  } = data;
825
- if (path().isAbsolute(rootDir)) throw new (_bitError().BitError)(`path is absolute, got ${rootDir}`);
826
- throwForExistingParentDir(bitMap, rootDir);
827
- const filtered = gitIgnore.filter(files);
828
- if (!filtered.length) {
829
- throw new (_exceptions().NoFiles)(files);
830
- }
831
- const componentFiles = filtered.map(match => {
832
- return {
833
- relativePath: (0, _legacy5().pathNormalizeToLinux)(match),
834
- name: path().basename(match)
835
- };
986
+ if (path().isAbsolute(data.rootDir)) throw new (_bitError().BitError)(`path is absolute, got ${data.rootDir}`);
987
+ const rootDir = normalizeRootDir(data.rootDir);
988
+ const componentId = _componentId().ComponentID.fromObject({
989
+ name: componentName
990
+ }, defaultScope);
991
+ // re-tracking the workspace root with the same id is a no-op, not a second owner
992
+ throwForExistingParentDir(bitMap, rootDir, componentId);
993
+ const isWorkspaceRoot = rootDir === _legacy3().WORKSPACE_ROOT_DIR;
994
+ // re-tracking keeps the entry's id, version included, the way "bit add" does: the id built from
995
+ // the name alone would replace it and make a snapped component look newly tracked.
996
+ const existingEntry = bitMap.getComponentIfExist(componentId, {
997
+ ignoreVersion: true
836
998
  });
999
+ const idToTrack = existingEntry?.id ?? componentId;
1000
+ // the workspace root has no entry point of its own, workspace.jsonc stands in for it, the way
1001
+ // "bit add ." resolves it (see determine-main-file). re-tracking keeps the entry's main file.
1002
+ const mainFile = data.mainFile ?? existingEntry?.mainFile ?? (isWorkspaceRoot ? _legacy2().WORKSPACE_JSONC : undefined);
1003
+ if (!mainFile) throw new (_bitError().BitError)(`unable to track "${rootDir}" as "${componentName}", no main file was given`);
1004
+ const existingConfig = isWorkspaceRoot ? existingEntry?.config : undefined;
1005
+ const componentFiles = isWorkspaceRoot ? await scanWorkspaceRootFiles(workspace, gitIgnore, batchRootDirs) : await filterResolvedFiles(rootDir, workspace.path, files, gitIgnore, trackAllFiles);
1006
+ // a nested component may take any file from a tracked workspace root but its main file, the
1007
+ // rule "bit add" applies. a root tracked later in the same call scans around this component.
1008
+ const workspaceRootMap = bitMap.getWorkspaceRootMap();
1009
+ if (!isWorkspaceRoot && workspaceRootMap) throwForTakingWorkspaceRootMainFile(workspaceRootMap, idToTrack, rootDir);
837
1010
  const componentMap = bitMap.addComponent({
838
- componentId: _componentId().ComponentID.fromObject({
839
- name: componentName
840
- }, defaultScope),
1011
+ componentId: idToTrack,
841
1012
  files: componentFiles,
842
- defaultScope,
843
- config,
1013
+ defaultScope: idToTrack.hasScope() ? undefined : defaultScope,
1014
+ config: isWorkspaceRoot ? configForWorkspaceRoot(existingConfig, config) : config,
844
1015
  mainFile,
845
1016
  rootDir
846
1017
  });
847
- return componentMap;
848
- });
1018
+ componentMaps.push(componentMap);
1019
+ }
849
1020
  const allIds = componentMaps.map(c => c.id);
850
1021
  await (0, _workspaceModules().linkToNodeModulesByIds)(workspace, allIds);
851
1022
  return allIds;
852
1023
  }
853
1024
 
1025
+ /**
1026
+ * the workspace-root component owns every file no other component claims, so it gives a file away to
1027
+ * a more specific component - all but its main file: without it, it fails to load from the next scan on.
1028
+ */
1029
+ function throwForTakingWorkspaceRootMainFile(workspaceRootMap, componentId, rootDir) {
1030
+ // checked on the directory rather than on the files the component keeps: the root's next scan
1031
+ // subtracts the whole directory, whether or not the component itself tracks that file. the
1032
+ // ownership lookups are case-insensitive, so this comparison is too.
1033
+ if (!workspaceRootMap.mainFile.toLowerCase().startsWith(`${rootDir.toLowerCase()}/`)) return;
1034
+ throw new (_bitError().BitError)(`unable to add "${rootDir}" as "${componentId.toString()}", it contains "${workspaceRootMap.mainFile}", the main file of the workspace-root component "${workspaceRootMap.id.toStringWithoutVersion()}". set a different main file for it first: bit add . --main ${_legacy2().WORKSPACE_JSONC}`);
1035
+ }
1036
+
1037
+ /**
1038
+ * the workspace-root's file-set is derived by scanning, the way every load derives it, rather than
1039
+ * taken from the caller: the scan is what knows the nested components (tracked already, or by the same
1040
+ * call), the ignore files below the root and bit's own exclusions. a main file the scan leaves out
1041
+ * fails the map validation before the map is written, not on the next load.
1042
+ */
1043
+ async function scanWorkspaceRootFiles(workspace, gitIgnore, batchRootDirs) {
1044
+ const nestedRootDirs = (0, _lodash().uniq)([...workspace.consumer.bitMap.getNestedRootDirs(_legacy3().WORKSPACE_ROOT_DIR), ...batchRootDirs.filter(dir => dir !== _legacy3().WORKSPACE_ROOT_DIR)]);
1045
+ const {
1046
+ trackAllFiles
1047
+ } = workspace.consumer.config;
1048
+ return (0, _legacy3().getFilesByDir)(_legacy3().WORKSPACE_ROOT_DIR, workspace.path, gitIgnore, nestedRootDirs, trackAllFiles);
1049
+ }
1050
+
1051
+ /**
1052
+ * the files the caller resolved for a component, minus the config files "bit ws-config write"
1053
+ * generates and the ignored ones - the rules the rescan applies, see getFilesByDir. the workspace
1054
+ * ignore rules are written against the workspace root, so a file is matched by its workspace-relative
1055
+ * path and mapped back to the component-relative one the map stores; the component's own ignore file
1056
+ * is applied to the latter.
1057
+ */
1058
+ async function filterResolvedFiles(rootDir, consumerPath, files, gitIgnore, trackAllFiles) {
1059
+ const notGenerated = files.map(_legacy5().pathNormalizeToLinux).filter(file => trackAllFiles || !_legacy2().IGNORE_ROOT_ONLY_LIST.includes(file));
1060
+ // what a scan never yields (bit's and git's own dirs, a nested workspace map) is not tracked here either,
1061
+ // or the next rescan would drop it
1062
+ const workspaceRelative = (0, _legacy3().filterByScanIgnorePatterns)(rootDir, notGenerated.map(file => path().posix.join(rootDir, file)));
1063
+ const filteredByWorkspaceRules = gitIgnore.filter(workspaceRelative).map(file => path().posix.relative(rootDir, file));
1064
+ const filtered = await (0, _legacy3().filterByOwnIgnoreFile)(rootDir, consumerPath, filteredByWorkspaceRules);
1065
+ if (!filtered.length) throw new (_exceptions().NoFiles)(files);
1066
+ return filtered.map(relativePath => ({
1067
+ relativePath,
1068
+ name: path().basename(relativePath),
1069
+ test: false
1070
+ }));
1071
+ }
1072
+
854
1073
  //# sourceMappingURL=add-components.js.map