@teambit/dependencies 1.0.82 → 1.0.83

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.
Files changed (43) hide show
  1. package/dependencies-loader/README.md +34 -0
  2. package/dependencies-loader/apply-overrides.ts +552 -0
  3. package/dependencies-loader/auto-detect-deps.ts +761 -0
  4. package/dependencies-loader/dependencies-data.ts +47 -0
  5. package/dependencies-loader/dependencies-loader.ts +174 -0
  6. package/dependencies-loader/dependencies-versions-resolver.ts +160 -0
  7. package/dependencies-loader/index.ts +2 -0
  8. package/dependencies-loader/overrides-dependencies.ts +115 -0
  9. package/dependencies-loader/package-to-definetly-typed.spec.ts +53 -0
  10. package/dependencies-loader/package-to-definetly-typed.ts +10 -0
  11. package/dist/dependencies-loader/README.md +34 -0
  12. package/dist/dependencies-loader/apply-overrides.d.ts +100 -0
  13. package/dist/dependencies-loader/apply-overrides.js +541 -0
  14. package/dist/dependencies-loader/apply-overrides.js.map +1 -0
  15. package/dist/dependencies-loader/auto-detect-deps.d.ts +165 -0
  16. package/dist/dependencies-loader/auto-detect-deps.js +768 -0
  17. package/dist/dependencies-loader/auto-detect-deps.js.map +1 -0
  18. package/dist/dependencies-loader/dependencies-data.d.ts +17 -0
  19. package/dist/dependencies-loader/dependencies-data.js +72 -0
  20. package/dist/dependencies-loader/dependencies-data.js.map +1 -0
  21. package/dist/dependencies-loader/dependencies-loader.d.ts +30 -0
  22. package/dist/dependencies-loader/dependencies-loader.js +229 -0
  23. package/dist/dependencies-loader/dependencies-loader.js.map +1 -0
  24. package/dist/dependencies-loader/dependencies-versions-resolver.d.ts +6 -0
  25. package/dist/dependencies-loader/dependencies-versions-resolver.js +153 -0
  26. package/dist/dependencies-loader/dependencies-versions-resolver.js.map +1 -0
  27. package/dist/dependencies-loader/index.d.ts +2 -0
  28. package/dist/dependencies-loader/index.js +33 -0
  29. package/dist/dependencies-loader/index.js.map +1 -0
  30. package/dist/dependencies-loader/overrides-dependencies.d.ts +17 -0
  31. package/dist/dependencies-loader/overrides-dependencies.js +118 -0
  32. package/dist/dependencies-loader/overrides-dependencies.js.map +1 -0
  33. package/dist/dependencies-loader/package-to-definetly-typed.d.ts +1 -0
  34. package/dist/dependencies-loader/package-to-definetly-typed.js +18 -0
  35. package/dist/dependencies-loader/package-to-definetly-typed.js.map +1 -0
  36. package/dist/dependencies-loader/package-to-definetly-typed.spec.d.ts +1 -0
  37. package/dist/dependencies-loader/package-to-definetly-typed.spec.js +27 -0
  38. package/dist/dependencies-loader/package-to-definetly-typed.spec.js.map +1 -0
  39. package/dist/dependencies.main.runtime.d.ts +21 -4
  40. package/dist/dependencies.main.runtime.js +49 -14
  41. package/dist/dependencies.main.runtime.js.map +1 -1
  42. package/package.json +17 -6
  43. /package/dist/{preview-1701813377689.js → preview-1701874572488.js} +0 -0
@@ -0,0 +1,761 @@
1
+ import * as path from 'path';
2
+ import fs from 'fs-extra';
3
+ import R from 'ramda';
4
+ import semver from 'semver';
5
+ import { isSnap } from '@teambit/component-version';
6
+ import { ComponentID } from '@teambit/component-id';
7
+ import { uniq, isEmpty } from 'lodash';
8
+ import { IssuesList, IssuesClasses } from '@teambit/component-issues';
9
+ import { Dependency } from '@teambit/legacy/dist/consumer/component/dependencies';
10
+ import { DEFAULT_DIST_DIRNAME, DEPENDENCIES_FIELDS } from '@teambit/legacy/dist/constants';
11
+ import Consumer from '@teambit/legacy/dist/consumer/consumer';
12
+ import logger from '@teambit/legacy/dist/logger/logger';
13
+ import { getExt, pathNormalizeToLinux, pathRelativeLinux } from '@teambit/legacy/dist/utils';
14
+ import { PathLinux, PathLinuxRelative, PathOsBased, removeFileExtension } from '@teambit/legacy/dist/utils/path';
15
+ import ComponentMap from '@teambit/legacy/dist/consumer/bit-map/component-map';
16
+ import Component from '@teambit/legacy/dist/consumer/component/consumer-component';
17
+ import { DependencyResolverMain } from '@teambit/dependency-resolver';
18
+ import { RelativePath } from '@teambit/legacy/dist/consumer/component/dependencies/dependency';
19
+ import { getDependencyTree } from '@teambit/legacy/dist/consumer/component/dependencies/files-dependency-builder';
20
+ import {
21
+ FileObject,
22
+ ImportSpecifier,
23
+ DependenciesTree,
24
+ } from '@teambit/legacy/dist/consumer/component/dependencies/files-dependency-builder/types/dependency-tree-type';
25
+ import { DevFilesMain } from '@teambit/dev-files';
26
+ import { Workspace } from '@teambit/workspace';
27
+ import { AspectLoaderMain, getCoreAspectPackageName } from '@teambit/aspect-loader';
28
+ import { ResolvedPackageData } from '@teambit/legacy/dist/utils/packages';
29
+ import { DependencyDetector } from '@teambit/legacy/dist/consumer/component/dependencies/files-dependency-builder/detector-hook';
30
+ import { packageToDefinetlyTyped } from './package-to-definetly-typed';
31
+ import { DependenciesData } from './dependencies-data';
32
+ import { AllDependencies, AllPackagesDependencies } from './apply-overrides';
33
+
34
+ export type FileType = {
35
+ isTestFile: boolean;
36
+ };
37
+
38
+ export type DebugDependencies = {
39
+ components: DebugComponentsDependency[];
40
+ unidentifiedPackages?: string[];
41
+ };
42
+
43
+ export type DebugComponentsDependency = {
44
+ id: ComponentID;
45
+ importSource?: string;
46
+ dependencyPackageJsonPath?: string;
47
+ dependentPackageJsonPath?: string;
48
+ // can be resolved here or can be any one of the strategies in dependencies-version-resolver
49
+ versionResolvedFrom?: 'DependencyPkgJson' | 'DependentPkgJson' | 'BitMap' | 'Model' | 'MergeConfig' | string;
50
+ version?: string;
51
+ componentIdResolvedFrom?: 'DependencyPkgJson' | 'DependencyPath';
52
+ packageName?: string;
53
+ };
54
+
55
+ export class AutoDetectDeps {
56
+ componentId: ComponentID;
57
+ componentMap: ComponentMap;
58
+ componentFromModel: Component;
59
+ consumerPath: PathOsBased;
60
+ tree: DependenciesTree;
61
+ allDependencies: AllDependencies;
62
+ allPackagesDependencies: AllPackagesDependencies;
63
+ issues: IssuesList;
64
+ coreAspects: string[] = [];
65
+ processedFiles: string[];
66
+ debugDependenciesData: DebugDependencies;
67
+ autoDetectConfigMerge: Record<string, any>;
68
+ constructor(
69
+ private component: Component,
70
+ private workspace: Workspace,
71
+ private devFiles: DevFilesMain,
72
+ private depsResolver: DependencyResolverMain,
73
+ private aspectLoader: AspectLoaderMain
74
+ ) {
75
+ this.componentId = component.componentId;
76
+ // the consumerComponent is coming from the workspace, so it must have the componentMap prop
77
+ this.componentMap = this.component.componentMap as ComponentMap;
78
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
79
+ this.componentFromModel = this.component.componentFromModel;
80
+ this.consumerPath = this.consumer.getPath();
81
+ this.allDependencies = {
82
+ dependencies: [],
83
+ devDependencies: [],
84
+ };
85
+ this.allPackagesDependencies = {
86
+ packageDependencies: {},
87
+ devPackageDependencies: {},
88
+ peerPackageDependencies: {},
89
+ };
90
+ this.processedFiles = [];
91
+ this.issues = component.issues;
92
+ this.debugDependenciesData = { components: [] };
93
+ }
94
+
95
+ get consumer(): Consumer {
96
+ return this.workspace.consumer;
97
+ }
98
+
99
+ private setTree(tree: DependenciesTree) {
100
+ this.tree = tree;
101
+ // console.log(JSON.stringify(tree, null, 4)); // uncomment to easily watch the tree received from bit-javascript
102
+ }
103
+
104
+ /**
105
+ * Resolve components and packages dependencies for a component.
106
+ * This method should NOT have any side-effect on the component. the DependenciesLoader class is
107
+ * responsible for saving this data on the component object.
108
+ *
109
+ * The process is as follows:
110
+ * 1) Use the language driver to parse the component files and find for each file its dependencies.
111
+ * 2) The results we get from the driver per file tells us what are the files and packages that depend on our file.
112
+ * and also whether there are missing packages and files.
113
+ * 3) Using the information from the driver, we go over each one of the dependencies files and find its counterpart
114
+ * component. The way how we find it, is by using the bit.map file which has a mapping between the component name and
115
+ * the file paths.
116
+ * 4) If we find a component to the file dependency, we add it to component.dependencies. Otherwise, it's added to
117
+ * component.issues.untrackedDependencies
118
+ * 5) Similarly, when we find the packages dependencies, they are added to component.packageDependencies. Otherwise,
119
+ * they're added to component.issues.missingPackagesDependenciesOnFs
120
+ * 6) In case the driver found a file dependency that is not on the file-system, we add that file to
121
+ * component.issues.missingDependenciesOnFs
122
+ */
123
+ async getDependenciesData(
124
+ cacheResolvedDependencies: Record<string, any>,
125
+ cacheProjectAst: Record<string, any> | undefined
126
+ ): Promise<{ dependenciesData: DependenciesData; debugDependenciesData: DebugDependencies }> {
127
+ const componentDir = path.join(this.consumerPath, this.componentMap.rootDir);
128
+ const { nonTestsFiles, testsFiles } = this.componentMap.getFilesGroupedByBeingTests();
129
+ const allFiles = [...nonTestsFiles, ...testsFiles];
130
+ const envDetectors = await this.getEnvDetectors();
131
+ // find the dependencies (internal files and packages) through automatic dependency resolution
132
+ const dependenciesTree = await getDependencyTree({
133
+ componentDir,
134
+ workspacePath: this.consumerPath,
135
+ filePaths: allFiles,
136
+ bindingPrefix: this.component.bindingPrefix,
137
+ visited: cacheResolvedDependencies,
138
+ cacheProjectAst,
139
+ envDetectors,
140
+ });
141
+ // we have the files dependencies, these files should be components that are registered in bit.map. Otherwise,
142
+ // they are referred as "untracked components" and the user should add them later on in order to tag
143
+ this.setTree(dependenciesTree.tree);
144
+ const devFiles = await this.devFiles.getDevFilesForConsumerComp(this.component);
145
+ await this.populateDependencies(allFiles, devFiles);
146
+ return {
147
+ dependenciesData: new DependenciesData(
148
+ this.allDependencies,
149
+ this.allPackagesDependencies,
150
+ this.issues,
151
+ this.coreAspects
152
+ ),
153
+ debugDependenciesData: this.debugDependenciesData,
154
+ };
155
+ }
156
+
157
+ async getEnvDetectors(): Promise<DependencyDetector[] | null> {
158
+ return this.depsResolver.calcComponentEnvDepDetectors(this.component.extensions);
159
+ }
160
+
161
+ /**
162
+ * Given the tree of file dependencies from the driver, find the components of these files.
163
+ * Each dependency file has a path, use bit.map to search for the component name by that path.
164
+ * If the component is found, add it to "this.allDependencies.dependencies". Otherwise, add it to "this.issues.untrackedDependencies".
165
+ *
166
+ * For the found components, add their sourceRelativePath and destinationRelativePath, they are being used for
167
+ * generating links upon import:
168
+ * sourceRelativePath - location of the link file.
169
+ * destinationRelativePath - destination written inside the link file.
170
+ *
171
+ * When a dependency is found in a regular (implementation) file, it goes to `dependencies`. If
172
+ * it found on a test file, it goes to `devDependencies`.
173
+ * Similarly, when a package is found in a regular file, it goes to `packageDependencies`. When
174
+ * if found in a test file, it goes to `devPackageDependencies`.
175
+ * An exception for the above is when a package is required in a regular or test file but is also
176
+ * mentioned in the `package.json` file as a peerDependency, in that case, the package is added
177
+ * to `peerPackageDependencies` and removed from other places. Unless this package is overridden
178
+ * and marked as ignored in the consumer or component config file.
179
+ */
180
+ private async populateDependencies(files: string[], testsFiles: string[]) {
181
+ files.forEach((file) => {
182
+ const fileType: FileType = {
183
+ isTestFile: testsFiles.includes(file),
184
+ };
185
+ this.throwForNonExistFile(file);
186
+ this.processCoreAspects(file);
187
+ this.processMissing(file, fileType);
188
+ this.processErrors(file);
189
+ this.processPackages(file, fileType);
190
+ this.processComponents(file, fileType);
191
+ this.processDepFiles(file, fileType);
192
+ this.processUnidentifiedPackages(file);
193
+ });
194
+ }
195
+
196
+ private throwForNonExistFile(file: string) {
197
+ if (!this.tree[file]) {
198
+ throw new Error(
199
+ `DependencyResolver: a file "${file}" was not returned from the driver, its dependencies are unknown`
200
+ );
201
+ }
202
+ }
203
+
204
+ private getComponentIdByResolvedPackageData(bit: ResolvedPackageData): ComponentID {
205
+ if (!bit.componentId) {
206
+ throw new Error(`resolved Bit component must have componentId prop in the package.json file`);
207
+ }
208
+ return bit.componentId;
209
+ }
210
+
211
+ /**
212
+ * this happens when using relative paths between components, which is allowed on Legacy only.
213
+ * on Harmony, during the execution of this function, it recognizes the use of relative-paths, enter
214
+ * it to the "issues", then, later, it shows a warning on bit-status and block tagging.
215
+ */
216
+ private getComponentIdByDepFile(depFile: PathLinux): {
217
+ componentId: ComponentID | undefined;
218
+ depFileRelative: PathLinux;
219
+ destination: string | null | undefined;
220
+ } {
221
+ let depFileRelative: PathLinux = depFile; // dependency file path relative to consumer root
222
+ let destination: string | null | undefined;
223
+ const rootDir = this.componentMap.rootDir;
224
+ // The depFileRelative is relative to rootDir, change it to be relative to current consumer.
225
+ // We can't use path.resolve(rootDir, fileDep) because this might not work when running
226
+ // bit commands not from root, because resolve take by default the process.cwd
227
+ const rootDirFullPath = path.join(this.consumerPath, rootDir);
228
+ const fullDepFile = path.resolve(rootDirFullPath, depFile);
229
+ depFileRelative = pathNormalizeToLinux(path.relative(this.consumerPath, fullDepFile));
230
+
231
+ const componentId = this.consumer.bitMap.getComponentIdByPath(depFileRelative);
232
+
233
+ return { componentId, depFileRelative, destination };
234
+ }
235
+
236
+ private processDepFiles(originFile: PathLinuxRelative, fileType: FileType, nested = false) {
237
+ // We don't just return because different files of the component might import different things from the depFile
238
+ // See more info here: https://github.com/teambit/bit/issues/1796
239
+ if (!this.processedFiles.includes(originFile)) {
240
+ this.processedFiles.push(originFile);
241
+ // We don't want to calculate nested files again after they calculated as direct files
242
+ } else if (nested) {
243
+ return;
244
+ }
245
+ const allDepsFiles = this.tree[originFile].files;
246
+ if (!allDepsFiles || R.isEmpty(allDepsFiles)) return;
247
+ allDepsFiles.forEach((depFile: FileObject) => {
248
+ const isDepFileUntracked = this.processOneDepFile(
249
+ originFile,
250
+ depFile.file,
251
+ depFile.importSpecifiers,
252
+ fileType,
253
+ depFile,
254
+ nested
255
+ );
256
+ // Only continue recursively if the dep file is untracked
257
+ // for tracked deps if they have untracked deps they will be shown under their own components
258
+ if (isDepFileUntracked) {
259
+ // Recursively check for untracked files (to show them all in bit status)
260
+ // for nested files we don't really care about the file types since we won't do all the checking
261
+ const dummyFileType: FileType = {
262
+ isTestFile: false,
263
+ };
264
+ this.processDepFiles(depFile.file, dummyFileType, true);
265
+ }
266
+ });
267
+ }
268
+
269
+ // return true if the dep file is untracked
270
+ private processOneDepFile(
271
+ originFile: PathLinuxRelative,
272
+ depFile: string,
273
+ importSpecifiers: ImportSpecifier[] | undefined,
274
+ fileType: FileType,
275
+ depFileObject: FileObject,
276
+ nested = false
277
+ ): boolean {
278
+ const { componentId, depFileRelative, destination } = this.getComponentIdByDepFile(depFile);
279
+ const importSource: string = depFileObject.importSource as string;
280
+ // the file dependency doesn't have any counterpart component. Add it to this.issues.untrackedDependencies
281
+ if (!componentId) {
282
+ this._pushToUntrackDependenciesIssues(originFile, depFileRelative, nested);
283
+ return true;
284
+ }
285
+ // happens when in the same component one file requires another one. In this case, there is
286
+ // noting to do regarding the dependencies
287
+ if (componentId.isEqual(this.componentId, { ignoreVersion: true })) {
288
+ return false;
289
+ }
290
+
291
+ const depComponentMap = this.consumer.bitMap.getComponentIfExist(componentId);
292
+ // found a dependency component. Add it to this.allDependencies.dependencies
293
+ const depRootDir = depComponentMap ? depComponentMap.rootDir : undefined;
294
+ const destinationRelativePath =
295
+ destination ||
296
+ (depRootDir && depFileRelative.startsWith(depRootDir)
297
+ ? pathRelativeLinux(depRootDir, depFileRelative)
298
+ : depFileRelative);
299
+
300
+ // when there is no rootDir for the current dependency (it happens when it's AUTHORED), keep the original path
301
+ const sourceRelativePath = depRootDir ? depFileRelative : depFile;
302
+
303
+ const depsPaths: RelativePath = {
304
+ sourceRelativePath,
305
+ destinationRelativePath,
306
+ };
307
+ if (importSpecifiers) {
308
+ importSpecifiers.forEach((importSpecifier) => {
309
+ if (importSpecifier.mainFile) delete importSpecifier.mainFile.exported;
310
+ });
311
+ depsPaths.importSpecifiers = importSpecifiers;
312
+ }
313
+ const currentComponentsDeps = new Dependency(componentId, [depsPaths]);
314
+ this._pushToRelativeComponentsAuthoredIssues(originFile, componentId, importSource, depsPaths);
315
+
316
+ const allDependencies: Dependency[] = [
317
+ ...this.allDependencies.dependencies,
318
+ ...this.allDependencies.devDependencies,
319
+ ];
320
+ const existingDependency = this.getExistingDependency(allDependencies, componentId);
321
+ if (existingDependency) {
322
+ const existingDepRelativePaths = this.getExistingDepRelativePaths(existingDependency, depsPaths);
323
+ if (!existingDepRelativePaths) {
324
+ // it is another file of an already existing component. Just add the new path
325
+ existingDependency.relativePaths.push(depsPaths);
326
+ return false;
327
+ }
328
+ // The dep path already exists but maybe this dep-file has more importSpecifiers
329
+ if (depsPaths.importSpecifiers) {
330
+ // add them to the existing dep
331
+ if (!existingDepRelativePaths.importSpecifiers) {
332
+ existingDepRelativePaths.importSpecifiers = [...depsPaths.importSpecifiers];
333
+ } else {
334
+ // both have importSpecifiers
335
+ const nonExistingImportSpecifiers = this.getDiffSpecifiers(
336
+ existingDepRelativePaths.importSpecifiers,
337
+ depsPaths.importSpecifiers
338
+ );
339
+ existingDepRelativePaths.importSpecifiers.push(...nonExistingImportSpecifiers);
340
+ }
341
+ }
342
+
343
+ if (depsPaths.importSource && !existingDepRelativePaths.importSource) {
344
+ existingDepRelativePaths.importSource = depsPaths.importSource;
345
+ }
346
+ } else {
347
+ const depDebug: DebugComponentsDependency = {
348
+ id: currentComponentsDeps.id,
349
+ importSource,
350
+ };
351
+ this.pushToDependenciesArray(currentComponentsDeps, fileType, depDebug);
352
+ }
353
+ return false;
354
+ }
355
+
356
+ /**
357
+ * process require/import of Bit components where the require statement is not a relative path
358
+ * but a module path, such as `require('@bit/bit.envs/compiler/babel');`
359
+ */
360
+ private processComponents(originFile: PathLinuxRelative, fileType: FileType) {
361
+ const components = this.tree[originFile].components;
362
+ if (!components || R.isEmpty(components)) return;
363
+ components.forEach((compDep) => {
364
+ let componentId = this.getComponentIdByResolvedPackageData(compDep);
365
+ if (componentId.isEqual(this.componentId)) {
366
+ // the component is importing itself, so ignore it. although currently it doesn't cause any issues, (probably
367
+ // because it filtered out later), it's better to remove it as soon as possible, for less-confusing debugging.
368
+ return;
369
+ }
370
+ const depDebug: DebugComponentsDependency = {
371
+ id: componentId,
372
+ dependencyPackageJsonPath: compDep.packageJsonPath,
373
+ dependentPackageJsonPath: compDep.dependentPackageJsonPath,
374
+ componentIdResolvedFrom: 'DependencyPkgJson',
375
+ packageName: compDep.name,
376
+ };
377
+ const getVersionFromPkgJson = (): string | null => {
378
+ const versionFromDependencyPkgJson = getValidVersion(compDep.concreteVersion);
379
+ if (versionFromDependencyPkgJson) {
380
+ depDebug.versionResolvedFrom = 'DependencyPkgJson';
381
+ return versionFromDependencyPkgJson;
382
+ }
383
+ const versionFromDependentPkgJson = getValidVersion(compDep.versionUsedByDependent);
384
+ if (versionFromDependentPkgJson) {
385
+ depDebug.versionResolvedFrom = 'DependentPkgJson';
386
+ return versionFromDependentPkgJson;
387
+ }
388
+ return null;
389
+ };
390
+ const version = getVersionFromPkgJson();
391
+ if (version) {
392
+ componentId = componentId.changeVersion(version);
393
+ }
394
+ const existingId = componentId;
395
+ if (existingId.isEqual(this.componentId)) {
396
+ // happens when one of the component files requires another using module path
397
+ // no need to enter anything to the dependencies
398
+ return;
399
+ }
400
+ this.addImportNonMainIssueIfNeeded(originFile, compDep);
401
+ const currentComponentsDeps = new Dependency(existingId, [], compDep.name);
402
+ this._pushToDependenciesIfNotExist(currentComponentsDeps, fileType, depDebug);
403
+ });
404
+ }
405
+
406
+ private isPkgInWorkspacePolicies(pkgName: string) {
407
+ return this.depsResolver.getWorkspacePolicyManifest().dependencies?.[pkgName];
408
+ }
409
+
410
+ private addImportNonMainIssueIfNeeded(filePath: PathLinuxRelative, dependencyPkgData: ResolvedPackageData) {
411
+ const depMain: PathLinuxRelative | undefined = dependencyPkgData.packageJsonContent?.main;
412
+ if (!depMain) {
413
+ return;
414
+ }
415
+ const depFullPath = pathNormalizeToLinux(dependencyPkgData.fullPath);
416
+
417
+ if (depFullPath.endsWith(depMain)) {
418
+ // it requires the main-file. all is good.
419
+ return;
420
+ }
421
+ const extDisallowNonMain = ['.ts', '.tsx', '.js', '.jsx'];
422
+ if (!extDisallowNonMain.includes(path.extname(depFullPath))) {
423
+ // some files such as scss/json are needed to be imported as non-main
424
+ return;
425
+ }
426
+ const pkgRootDir = dependencyPkgData.packageJsonContent?.componentRootFolder;
427
+ if (pkgRootDir && !fs.existsSync(path.join(pkgRootDir, DEFAULT_DIST_DIRNAME))) {
428
+ // the dependency wasn't compiled yet. the issue is probably because depMain points to the dist
429
+ // and depFullPath is in the source.
430
+ return;
431
+ }
432
+ const nonMainFileSplit = depFullPath.split(`node_modules/`);
433
+ const nonMainFileShort = nonMainFileSplit[1] || nonMainFileSplit[0];
434
+ if (nonMainFileShort.includes('eslintrc')) {
435
+ // a temporary workaround for envs that don't expose eslintrc config in their index file.
436
+ // this is needed for a future change of detecting require.resolve syntax
437
+ return;
438
+ }
439
+ (this.issues.getOrCreate(IssuesClasses.ImportNonMainFiles).data[filePath] ||= []).push(nonMainFileShort);
440
+ }
441
+
442
+ private processPackages(originFile: PathLinuxRelative, fileType: FileType) {
443
+ const packages = this.tree[originFile].packages;
444
+ if (this.componentFromModel) {
445
+ const modelDeps = this.componentFromModel.getAllPackageDependencies();
446
+ // If a package is not in the policies, then we resolve the package from the model.
447
+ for (const pkgName of Object.keys(packages)) {
448
+ if (!this.isPkgInWorkspacePolicies(pkgName) && modelDeps[pkgName]) {
449
+ packages[pkgName] = modelDeps[pkgName];
450
+ }
451
+ }
452
+ }
453
+ const packageNames = Object.keys(packages).concat(this.tree[originFile].missing?.packages ?? []);
454
+ this._addTypesPackagesForTypeScript(packageNames, originFile);
455
+ if (!packages || R.isEmpty(packages)) return;
456
+ if (fileType.isTestFile) {
457
+ Object.assign(this.allPackagesDependencies.devPackageDependencies, packages);
458
+ } else {
459
+ Object.assign(this.allPackagesDependencies.packageDependencies, packages);
460
+ }
461
+ }
462
+
463
+ private processMissing(originFile: PathLinuxRelative, fileType: FileType) {
464
+ const missing = this.tree[originFile].missing;
465
+ if (!missing) return;
466
+ const processMissingFiles = () => {
467
+ if (isEmpty(missing.files)) return;
468
+ const missingFiles = missing.files.filter((file) => {
469
+ const hasExtension = Boolean(path.extname(file));
470
+ if (!hasExtension) return true;
471
+ // the missing file has extension, e.g. "index.js". It's possible that this file doesn't exist in the source
472
+ // but will be available in the dists. so if found same filename without the extension, we assume it's fine.
473
+ const rootDirAbs = this.consumer.toAbsolutePath(this.componentMap.rootDir);
474
+ const filePathAbs = path.resolve(rootDirAbs, file);
475
+ const relativeToCompDir = path.relative(rootDirAbs, filePathAbs);
476
+ const relativeToCompDirWithoutExt = removeFileExtension(relativeToCompDir);
477
+ const compFilesWithoutExt = this.componentMap.getAllFilesPaths().map((f) => removeFileExtension(f));
478
+ const existWithDifferentExt = compFilesWithoutExt.some((f) => f === relativeToCompDirWithoutExt);
479
+ return !existWithDifferentExt;
480
+ });
481
+ if (R.isEmpty(missingFiles)) return;
482
+ this._pushToMissingDependenciesOnFs(originFile, missingFiles);
483
+ };
484
+ const processMissingPackages = () => {
485
+ if (isEmpty(missing.packages)) return;
486
+ const missingPackages = missing.packages;
487
+ if (!R.isEmpty(missingPackages)) {
488
+ this._pushToMissingPackagesDependenciesIssues(originFile, missingPackages, fileType);
489
+ }
490
+ };
491
+ processMissingFiles();
492
+ processMissingPackages();
493
+ }
494
+
495
+ private processErrors(originFile: PathLinuxRelative) {
496
+ const error: any = this.tree[originFile].error;
497
+ if (!error) return;
498
+ logger.errorAndAddBreadCrumb(
499
+ 'dependency-resolver.processErrors',
500
+ 'got an error from the driver while resolving dependencies'
501
+ );
502
+ logger.error('dependency-resolver.processErrors', error);
503
+ if (error.code === 'PARSING_ERROR') {
504
+ const location = error.lineNumber && error.column ? ` (line: ${error.lineNumber}, column: ${error.column})` : '';
505
+ this.issues.getOrCreate(IssuesClasses.ParseErrors).data[originFile] = error.message + location;
506
+ } else this.issues.getOrCreate(IssuesClasses.ResolveErrors).data[originFile] = error.message;
507
+ }
508
+
509
+ private getCoreAspectsPackagesAndIds(): Record<string, string> {
510
+ const allCoreAspectsIds = this.aspectLoader.getCoreAspectIds();
511
+ const coreAspectsPackagesAndIds = {};
512
+
513
+ allCoreAspectsIds.forEach((id) => {
514
+ const packageName = getCoreAspectPackageName(id);
515
+ coreAspectsPackagesAndIds[packageName] = id;
516
+ });
517
+
518
+ return coreAspectsPackagesAndIds;
519
+ }
520
+
521
+ /**
522
+ * when a user uses core-extensions these core-extensions should not be dependencies.
523
+ * here, we filter them out from all places they could entered as dependencies.
524
+ * an exception is when running this method on bit-core-extensions themselves (dogfooding), in
525
+ * which case we recognizes that the current originFile is a core-extension and avoid filtering.
526
+ */
527
+ private processCoreAspects(originFile: PathLinuxRelative) {
528
+ const coreAspects = this.getCoreAspectsPackagesAndIds();
529
+
530
+ // const scopes = coreAspects.map((id) => {
531
+ // const id = id.split()
532
+ // });
533
+
534
+ const coreAspectIds = Object.values(coreAspects);
535
+ if (coreAspectIds.includes(this.component.id.toStringWithoutVersion())) {
536
+ return;
537
+ }
538
+
539
+ const coreAspectsPackages = Object.keys(coreAspects);
540
+
541
+ const components = this.tree[originFile].components;
542
+ const unidentifiedPackages = this.tree[originFile].unidentifiedPackages;
543
+ const usedCoreAspects: string[] = [];
544
+
545
+ const findMatchingCoreAspect = (packageName: string) => {
546
+ return coreAspectsPackages.find((coreAspectName) => packageName === coreAspectName);
547
+ };
548
+ const unidentifiedPackagesFiltered = unidentifiedPackages?.filter((packageName) => {
549
+ const matchingCoreAspectPackageName = findMatchingCoreAspect(packageName);
550
+ if (matchingCoreAspectPackageName) {
551
+ usedCoreAspects.push(coreAspects[matchingCoreAspectPackageName]);
552
+ }
553
+ return !matchingCoreAspectPackageName;
554
+ });
555
+ const bitsFiltered = components?.filter((packageInfo) => {
556
+ const matchingCoreAspectPackageName = findMatchingCoreAspect(packageInfo.name);
557
+ if (matchingCoreAspectPackageName) {
558
+ usedCoreAspects.push(coreAspects[matchingCoreAspectPackageName]);
559
+ }
560
+ return !matchingCoreAspectPackageName;
561
+ });
562
+
563
+ this.tree[originFile].unidentifiedPackages = unidentifiedPackagesFiltered;
564
+ this.tree[originFile].components = bitsFiltered;
565
+ this.coreAspects.push(...R.uniq(usedCoreAspects));
566
+ }
567
+
568
+ /**
569
+ * ** LEGACY ONLY **
570
+ * This is related to a legacy feature "custom-module-resolution". the code was removed, only the debug is still there, just in case.
571
+ *
572
+ * ** OLD COMMENT **
573
+ * currently the only unidentified packages being process are the ones coming from custom-modules-resolution.
574
+ * assuming the author used custom-resolution, which enable using non-relative import syntax,
575
+ * for example, requiring the file 'src/utils/is-string' from anywhere as require('utils/is-string');
576
+ * now, when the component is imported, the driver recognizes 'utils/is-string' as a package,
577
+ * because it's not relative.
578
+ * the goal here is to use the 'package' the driver found and match it with one of the
579
+ * dependencies from the model. In the example above, we might find in the model, a dependency
580
+ * is-string with importSource of 'utils/is-string'.
581
+ * Once a match is found, copy the relativePaths from the model.
582
+ *
583
+ * keep in mind that this custom-modules-resolution supported on legacy components only.
584
+ * as such, no need to find the packageName to pass to _pushToDependenciesIfNotExist method.
585
+ */
586
+ private processUnidentifiedPackages(originFile: PathLinuxRelative) {
587
+ const unidentifiedPackages = this.tree[originFile].unidentifiedPackages;
588
+ if (!unidentifiedPackages || !unidentifiedPackages.length) return;
589
+ this.debugDependenciesData.unidentifiedPackages = unidentifiedPackages;
590
+ }
591
+
592
+ private _pushToDependenciesIfNotExist(
593
+ dependency: Dependency,
594
+ fileType: FileType,
595
+ depDebug: DebugComponentsDependency
596
+ ) {
597
+ const existingDependency = this.getExistingDependency(this.allDependencies.dependencies, dependency.id);
598
+ const existingDevDependency = this.getExistingDependency(this.allDependencies.devDependencies, dependency.id);
599
+ // no need to enter dev dependency to devDependencies if it exists already in dependencies
600
+ if (existingDependency || (existingDevDependency && fileType.isTestFile)) {
601
+ return;
602
+ }
603
+ // at this point, either, it doesn't exist at all and should be entered.
604
+ // or it exists in devDependencies but now it comes from non-dev file, which should be entered
605
+ // as non-dev.
606
+ this.pushToDependenciesArray(dependency, fileType, depDebug);
607
+ }
608
+
609
+ private pushToDependenciesArray(
610
+ currentComponentsDeps: Dependency,
611
+ fileType: FileType,
612
+ depDebug: DebugComponentsDependency
613
+ ) {
614
+ if (fileType.isTestFile) {
615
+ this.allDependencies.devDependencies.push(currentComponentsDeps);
616
+ } else {
617
+ this.allDependencies.dependencies.push(currentComponentsDeps);
618
+ }
619
+ this.debugDependenciesData.components.push(depDebug);
620
+ }
621
+
622
+ private getExistingDependency(dependencies: Dependency[], id: ComponentID): Dependency | null | undefined {
623
+ return dependencies.find((d) => d.id.isEqualWithoutVersion(id));
624
+ }
625
+
626
+ private getExistingDepRelativePaths(dependency: Dependency, relativePath: RelativePath) {
627
+ if (!dependency.relativePaths || R.isEmpty(dependency.relativePaths)) return null;
628
+ return dependency.relativePaths.find(
629
+ (paths) =>
630
+ paths.sourceRelativePath === relativePath.sourceRelativePath &&
631
+ paths.destinationRelativePath === relativePath.destinationRelativePath
632
+ );
633
+ }
634
+
635
+ private getDiffSpecifiers(originSpecifiers: ImportSpecifier[], targetSpecifiers: ImportSpecifier[]) {
636
+ const cmp = (specifier1, specifier2) => specifier1.mainFile.name === specifier2.mainFile.name;
637
+ return R.differenceWith(cmp, targetSpecifiers, originSpecifiers);
638
+ }
639
+
640
+ /**
641
+ * when requiring packages in typescript, sometimes there are the types packages with the same
642
+ * name, which the user probably wants as well. for example, requiring `foo` package, will also
643
+ * add `@types/foo` to the devDependencies if it has been found in the user `package.json` file.
644
+ *
645
+ * ideally this should be in bit-javascript. however, the decision where to put these `@types`
646
+ * packages (dependencies/devDependencies) is done here according to the user `package.json`
647
+ * and can't be done there because the `Tree` we get from bit-javascript doesn't have this
648
+ * distinction.
649
+ */
650
+ private _addTypesPackagesForTypeScript(packageNames: string[], originFile: PathLinuxRelative): void {
651
+ if (packageNames.length === 0) return;
652
+ const isTypeScript = getExt(originFile) === 'ts' || getExt(originFile) === 'tsx';
653
+ if (!isTypeScript) return;
654
+ const depsHost = this.depsResolver.getWorkspacePolicyManifest();
655
+ const addFromConfig = (packageName: string): boolean => {
656
+ if (!depsHost) return false;
657
+ return DEPENDENCIES_FIELDS.some((depField) => {
658
+ if (!depsHost[depField]) return false;
659
+ const typesPackage = packageToDefinetlyTyped(packageName);
660
+ if (!depsHost[depField][typesPackage]) return false;
661
+ Object.assign(this.allPackagesDependencies.devPackageDependencies, {
662
+ [typesPackage]: depsHost[depField][typesPackage],
663
+ });
664
+ return true;
665
+ });
666
+ };
667
+ const addFromModel = (packageName: string) => {
668
+ if (!this.componentFromModel) return;
669
+ const typesPackage = packageToDefinetlyTyped(packageName);
670
+ const typedPackageFromModel = this.componentFromModel.devPackageDependencies[typesPackage];
671
+ if (!typedPackageFromModel) return;
672
+ Object.assign(this.allPackagesDependencies.devPackageDependencies, {
673
+ [typesPackage]: typedPackageFromModel,
674
+ });
675
+ };
676
+
677
+ packageNames.forEach((packageName) => {
678
+ const added = addFromConfig(packageName);
679
+ if (!added) addFromModel(packageName);
680
+ });
681
+ }
682
+
683
+ private _pushToUntrackDependenciesIssues(originFile: PathLinuxRelative, depFileRelative, nested = false) {
684
+ const findExisting = () => {
685
+ let result;
686
+ R.forEachObjIndexed((currentUntracked) => {
687
+ const found = currentUntracked.untrackedFiles.find((file) => {
688
+ return file.relativePath === depFileRelative;
689
+ });
690
+ if (found) {
691
+ result = found;
692
+ }
693
+ }, this.issues.getIssue(IssuesClasses.UntrackedDependencies)?.data || {});
694
+ return result;
695
+ };
696
+ const existing = findExisting();
697
+ const newUntrackedFile = { relativePath: depFileRelative, existing: false };
698
+ // If it's already found mark them both as existing
699
+ if (existing) {
700
+ newUntrackedFile.existing = true;
701
+ existing.existing = true;
702
+ }
703
+ const untrackIssue = this.issues.getOrCreate(IssuesClasses.UntrackedDependencies);
704
+ const untrackedCurrentFile = untrackIssue?.data[originFile];
705
+ if (untrackedCurrentFile) {
706
+ untrackedCurrentFile.untrackedFiles.push(newUntrackedFile);
707
+ } else {
708
+ untrackIssue.data[originFile] = { nested, untrackedFiles: [newUntrackedFile] };
709
+ }
710
+ }
711
+ private _pushToRelativeComponentsAuthoredIssues(
712
+ originFile,
713
+ componentId: ComponentID,
714
+ importSource: string,
715
+ relativePath: RelativePath
716
+ ) {
717
+ (this.issues.getOrCreate(IssuesClasses.RelativeComponentsAuthored).data[originFile] ||= []).push({
718
+ importSource,
719
+ componentId,
720
+ relativePath,
721
+ });
722
+ }
723
+ private _pushToMissingDependenciesOnFs(originFile: PathLinuxRelative, missingFiles: string[]) {
724
+ (this.issues.getOrCreate(IssuesClasses.MissingDependenciesOnFs).data[originFile] ||= []).push(...missingFiles);
725
+ }
726
+ private _pushToMissingPackagesDependenciesIssues(
727
+ originFile: PathLinuxRelative,
728
+ missingPackages: string[],
729
+ fileType: FileType
730
+ ) {
731
+ const data = this.issues.getOrCreate(IssuesClasses.MissingPackagesDependenciesOnFs).data;
732
+ const foundFile = data.find((file) => file.filePath === originFile);
733
+ if (foundFile) {
734
+ foundFile.missingPackages = uniq([...missingPackages, ...foundFile.missingPackages]);
735
+ } else {
736
+ data.push({ filePath: originFile, missingPackages, isDevFile: fileType.isTestFile });
737
+ }
738
+ }
739
+ }
740
+
741
+ export function getValidVersion(version: string | undefined) {
742
+ if (!version) {
743
+ return null;
744
+ }
745
+ if (semver.valid(version)) {
746
+ // this takes care of pre-releases as well, as they're considered valid semver.
747
+ return version;
748
+ }
749
+ if (semver.validRange(version)) {
750
+ // if this is a range, e.g. ^1.0.0, return a valid version: 1.0.0.
751
+ const coerced = semver.coerce(version);
752
+ if (coerced) {
753
+ return coerced.version;
754
+ }
755
+ }
756
+ if (isSnap(version)) {
757
+ return version;
758
+ }
759
+ // it's probably a relative path to the component
760
+ return null;
761
+ }