diff --git a/scopes/component/remove/index.ts b/scopes/component/remove/index.ts index 374446234841..efac4a6bca85 100644 --- a/scopes/component/remove/index.ts +++ b/scopes/component/remove/index.ts @@ -4,5 +4,6 @@ export type { RemoveMain, RemoveInfo } from './remove.main.runtime'; export type { RemovedLocalObjects } from './removed-local-objects'; export { deleteComponentsFiles } from './delete-component-files'; export { removeTemplate } from './remove-template'; +export { removeComponentsFromNodeModules } from './remove-components'; export default RemoveAspect; export { RemoveAspect }; diff --git a/scopes/component/remove/remove-components.ts b/scopes/component/remove/remove-components.ts index da5490b14161..36835c762207 100644 --- a/scopes/component/remove/remove-components.ts +++ b/scopes/component/remove/remove-components.ts @@ -1,8 +1,9 @@ +import fs from 'fs-extra'; import groupArray from 'group-array'; import partition from 'lodash.partition'; import { Workspace } from '@teambit/workspace'; import { ComponentIdList } from '@teambit/component-id'; -import { isEmpty } from 'lodash'; +import { compact, isEmpty } from 'lodash'; import { CENTRAL_BIT_HUB_NAME, CENTRAL_BIT_HUB_URL, LATEST_BIT_VERSION } from '@teambit/legacy/dist/constants'; import { BitError } from '@teambit/bit-error'; import enrichContextFromGlobal from '@teambit/legacy/dist/hooks/utils/enrich-context-from-global'; @@ -14,8 +15,10 @@ import { deleteComponentsFiles } from './delete-component-files'; import ComponentsList from '@teambit/legacy/dist/consumer/component/components-list'; import Component from '@teambit/legacy/dist/consumer/component/consumer-component'; import RemovedObjects from '@teambit/legacy/dist/scope/removed-components'; -import * as packageJsonUtils from '@teambit/legacy/dist/consumer/component/package-json-utils'; import pMapSeries from 'p-map-series'; +import { Consumer } from '@teambit/legacy/dist/consumer'; +import ConsumerComponent from '@teambit/legacy/dist/consumer/component'; +import { getNodeModulesPathOfComponent } from '@teambit/pkg.modules.component-package-name'; import { RemovedLocalObjects } from './removed-local-objects'; export type RemoveComponentsResult = { localResult: RemovedLocalObjects; remoteResult: RemovedObjects[] }; @@ -156,7 +159,7 @@ If you understand the risks and wish to proceed with the removal, please use the const idsToCleanFromWorkspace = ComponentIdList.fromArray( idsToRemove.filter((id) => newComponents.hasWithoutVersion(id)) ); - const { components: componentsToRemove, invalidComponents } = await consumer.loadComponents(idsToRemove, false); + const { components: componentsToRemove } = await consumer.loadComponents(idsToRemove, false); const { removedComponentIds, missingComponents, dependentBits, removedFromLane } = await consumer.scope.removeMany( idsToRemoveFromScope, force, @@ -167,13 +170,9 @@ If you understand the risks and wish to proceed with the removal, please use the if (idsToCleanFromWorkspace.length) { if (deleteFiles) await deleteComponentsFiles(consumer, idsToCleanFromWorkspace); if (!track) { - const invalidComponentsIds = invalidComponents.map((i) => i.id); const removedComponents = componentsToRemove.filter((c) => idsToCleanFromWorkspace.hasWithoutVersion(c.id)); - await packageJsonUtils.removeComponentsFromWorkspacesAndDependencies( - consumer, - removedComponents, - invalidComponentsIds - ); + await consumer.packageJson.removeComponentsFromDependencies(removedComponents); + await removeComponentsFromNodeModules(consumer, removedComponents); await consumer.cleanFromBitMap(idsToCleanFromWorkspace); await workspace.cleanFromConfig(idsToCleanFromWorkspace); } @@ -186,3 +185,13 @@ If you understand the risks and wish to proceed with the removal, please use the removedFromLane ); } + +export async function removeComponentsFromNodeModules(consumer: Consumer, components: ConsumerComponent[]) { + logger.debug(`removeComponentsFromNodeModules: ${components.map((c) => c.id.toString()).join(', ')}`); + const pathsToRemoveWithNulls = components.map((c) => { + return getNodeModulesPathOfComponent({ ...c, id: c.id }); + }); + const pathsToRemove = compact(pathsToRemoveWithNulls); + logger.debug(`deleting the following paths: ${pathsToRemove.join('\n')}`); + return Promise.all(pathsToRemove.map((componentPath) => fs.remove(consumer.toAbsolutePath(componentPath)))); +} diff --git a/scopes/component/remove/remove.main.runtime.ts b/scopes/component/remove/remove.main.runtime.ts index 5456c67578b6..0d0843130eb2 100644 --- a/scopes/component/remove/remove.main.runtime.ts +++ b/scopes/component/remove/remove.main.runtime.ts @@ -14,10 +14,9 @@ import { IssuesAspect, IssuesMain } from '@teambit/issues'; import pMapSeries from 'p-map-series'; import { NoHeadNoVersion } from '@teambit/legacy/dist/scope/exceptions/no-head-no-version'; import { ComponentAspect, Component, ComponentMain } from '@teambit/component'; -import { removeComponentsFromNodeModules } from '@teambit/legacy/dist/consumer/component/package-json-utils'; import { deleteComponentsFiles } from './delete-component-files'; import { RemoveCmd } from './remove-cmd'; -import { RemoveComponentsResult, removeComponents } from './remove-components'; +import { RemoveComponentsResult, removeComponents, removeComponentsFromNodeModules } from './remove-components'; import { RemoveAspect } from './remove.aspect'; import { RemoveFragment } from './remove.fragment'; import { RecoverCmd, RecoverOptions } from './recover-cmd'; diff --git a/scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts b/scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts index b7a865882abe..a85b169dd4c6 100644 --- a/scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts +++ b/scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts @@ -456,7 +456,7 @@ export class AutoDetectDeps { // some files such as scss/json are needed to be imported as non-main return; } - const pkgRootDir = dependencyPkgData.packageJsonContent?.componentRootFolder; + const pkgRootDir = dependencyPkgData.packageJsonPath && path.dirname(dependencyPkgData.packageJsonPath); if (pkgRootDir && !fs.existsSync(path.join(pkgRootDir, DEFAULT_DIST_DIRNAME))) { // the dependency wasn't compiled yet. the issue is probably because depMain points to the dist // and depFullPath is in the source. diff --git a/scopes/dependencies/dependencies/resolve-pkg-data.ts b/scopes/dependencies/dependencies/resolve-pkg-data.ts index 93050840dba8..f9e22db0f10a 100644 --- a/scopes/dependencies/dependencies/resolve-pkg-data.ts +++ b/scopes/dependencies/dependencies/resolve-pkg-data.ts @@ -2,7 +2,7 @@ import { ComponentID } from '@teambit/component-id'; import path from 'path'; import readPkgUp from 'read-pkg-up'; import { PACKAGE_JSON } from '@teambit/legacy/dist/constants'; -import PackageJson from '@teambit/legacy/dist/consumer/component/package-json'; +import PackageJsonFile from '@teambit/legacy/dist/consumer/component/package-json-file'; import { PathLinuxAbsolute, PathOsBased, PathOsBasedAbsolute } from '@teambit/toolbox.path.path'; import { resolvePackageNameByPath } from '@teambit/legacy.utils'; @@ -83,8 +83,8 @@ function enrichDataFromDependency(packageData: ResolvedPackageData) { // if you have 2 authored component which one dependent on the other // we will look for the package.json on the dependency but won't find it // if we propagate we will take the version from the root's package json which has nothing with the component version - // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! - const packageInfo = PackageJson.loadSync(packageDir); + const packageJsonFile = PackageJsonFile.loadSync(packageDir); + const packageInfo = packageJsonFile.packageJsonObject; // the version can be empty when creating the package.json for author, or when using custom-module-resolution // that's fine, we still need the component-id in this case. diff --git a/scopes/workspace/eject/components-ejector.ts b/scopes/workspace/eject/components-ejector.ts index 49f42730ca1c..10b11961fc31 100644 --- a/scopes/workspace/eject/components-ejector.ts +++ b/scopes/workspace/eject/components-ejector.ts @@ -14,12 +14,11 @@ import defaultErrorHandler from '@teambit/legacy/dist/cli/default-error-handler' import { getScopeRemotes } from '@teambit/legacy/dist/scope/scope-remotes'; import { componentIdToPackageName } from '@teambit/pkg.modules.component-package-name'; import Component from '@teambit/legacy/dist/consumer/component/consumer-component'; -import PackageJsonFile from '@teambit/legacy/dist/consumer/component/package-json-file'; -import * as packageJsonUtils from '@teambit/legacy/dist/consumer/component/package-json-utils'; import DataToPersist from '@teambit/legacy/dist/consumer/component/sources/data-to-persist'; import RemovePath from '@teambit/legacy/dist/consumer/component/sources/remove-path'; import { Logger } from '@teambit/logger'; import { InstallMain } from '@teambit/install'; +import { removeComponentsFromNodeModules } from '@teambit/remove'; export type EjectResults = { ejectedComponents: ComponentIdList; @@ -46,8 +45,6 @@ export class ComponentsEjector { // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! notEjectedDependents: Array<{ dependent: Component; ejectedDependencies: Component[] }>; failedComponents: FailedComponents; - // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! - packageJsonFilesBeforeChanges: PackageJsonFile[]; // for rollback in case of errors constructor( private workspace: Workspace, private install: InstallMain, @@ -127,7 +124,7 @@ export class ComponentsEjector { const action = 'Eject: removing the existing components from node_modules'; this.logger.setStatusLine(action); this.logger.debug(action); - await packageJsonUtils.removeComponentsFromNodeModules(this.consumer, this.componentsToEject); + await removeComponentsFromNodeModules(this.consumer, this.componentsToEject); this.logger.consoleSuccess(action); } diff --git a/src/consumer/component/package-json-file.ts b/src/consumer/component/package-json-file.ts index d34e560a9c5f..ea556da003ff 100644 --- a/src/consumer/component/package-json-file.ts +++ b/src/consumer/component/package-json-file.ts @@ -181,13 +181,11 @@ export default class PackageJsonFile { } addDependencies(dependencies: Record) { - // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! this.packageJsonObject.dependencies = Object.assign({}, this.packageJsonObject.dependencies, dependencies); } addDevDependencies(dependencies: Record) { - // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! this.packageJsonObject.devDependencies = Object.assign({}, this.packageJsonObject.devDependencies, dependencies); } @@ -241,6 +239,19 @@ export default class PackageJsonFile { this.packageJsonObject = Object.assign(this.packageJsonObject, packageJsonObject); } + /* + * remove components from package.json dependencies + */ + async removeComponentsFromDependencies(components: Component[]) { + if (!this.fileExist) return; + const deps = this.packageJsonObject.dependencies; + if (!deps) return; + components.forEach((c) => { + this.removeDependency(componentIdToPackageName(c)); + }); + await this.write(); + } + clone(): PackageJsonFile { // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! const clone = new PackageJsonFile(this); diff --git a/src/consumer/component/package-json-utils.ts b/src/consumer/component/package-json-utils.ts deleted file mode 100644 index 1f6dcb10af88..000000000000 --- a/src/consumer/component/package-json-utils.ts +++ /dev/null @@ -1,59 +0,0 @@ -import fs from 'fs-extra'; -import R from 'ramda'; -import { compact } from 'lodash'; -import { ComponentID } from '@teambit/component-id'; -import logger from '../../logger/logger'; -import { PathOsBasedAbsolute } from '@teambit/toolbox.path.path'; -import { componentIdToPackageName, getNodeModulesPathOfComponent } from '@teambit/pkg.modules.component-package-name'; -import Component from '../component/consumer-component'; -import Consumer from '../consumer'; -import PackageJson from './package-json'; -import PackageJsonFile from './package-json-file'; - -export async function addComponentsWithVersionToRoot(consumer: Consumer, components: Component[]) { - const componentsToAdd = R.fromPairs( - components.map((component) => { - const packageName = componentIdToPackageName(component); - return [packageName, component.version]; - }) - ); - await _addDependenciesPackagesIntoPackageJson(consumer.getPath(), componentsToAdd); -} - -export async function removeComponentsFromWorkspacesAndDependencies( - consumer: Consumer, - components: Component[], - invalidComponents: ComponentID[] = [] -) { - const bitIds = [...components.map((c) => c.id), ...invalidComponents]; - const rootDir = consumer.getPath(); - if ( - consumer.config._manageWorkspaces && - consumer.config.packageManager === 'yarn' && - consumer.config._useWorkspaces - ) { - const dirsToRemove = bitIds.map((id) => consumer.bitMap.getComponent(id, { ignoreVersion: true }).rootDir); - if (dirsToRemove && dirsToRemove.length) { - const dirsToRemoveWithoutEmpty = compact(dirsToRemove); - await PackageJson.removeComponentsFromWorkspaces(rootDir, dirsToRemoveWithoutEmpty); - } - } - await PackageJson.removeComponentsFromDependencies(rootDir, components); - await removeComponentsFromNodeModules(consumer, components); -} - -async function _addDependenciesPackagesIntoPackageJson(dir: PathOsBasedAbsolute, dependencies: Record) { - const packageJsonFile = await PackageJsonFile.load(dir); - packageJsonFile.addDependencies(dependencies); - await packageJsonFile.write(); -} - -export async function removeComponentsFromNodeModules(consumer: Consumer, components: Component[]) { - logger.debug(`removeComponentsFromNodeModules: ${components.map((c) => c.id.toString()).join(', ')}`); - const pathsToRemoveWithNulls = components.map((c) => { - return getNodeModulesPathOfComponent({ ...c, id: c.id }); - }); - const pathsToRemove = compact(pathsToRemoveWithNulls); - logger.debug(`deleting the following paths: ${pathsToRemove.join('\n')}`); - return Promise.all(pathsToRemove.map((componentPath) => fs.remove(consumer.toAbsolutePath(componentPath)))); -} diff --git a/src/consumer/component/package-json.ts b/src/consumer/component/package-json.ts deleted file mode 100644 index eb159365e172..000000000000 --- a/src/consumer/component/package-json.ts +++ /dev/null @@ -1,178 +0,0 @@ -import fs from 'fs-extra'; -import path from 'path'; -import { BitId } from '@teambit/legacy-bit-id'; -import { PACKAGE_JSON } from '../../constants'; -import { componentIdToPackageName } from '@teambit/pkg.modules.component-package-name'; -import ConsumerComponent from '.'; -import logger from '../../logger/logger'; - -function composePath(componentRootFolder: string) { - return path.join(componentRootFolder, PACKAGE_JSON); -} - -export type PackageJsonProps = { - name?: string; - version?: string; - homepage?: string; - main?: string; - dependencies?: Record; - devDependencies?: Record; - peerDependencies?: Record; - license?: string; - scripts?: Record; - workspaces?: string[]; - private?: boolean; - componentId?: BitId; - exported?: boolean; - bit?: Record; -}; - -export default class PackageJson { - name?: string; - version?: string; - homepage?: string; - main?: string; - dependencies?: Record; - devDependencies?: Record; - peerDependencies?: Record; - componentRootFolder?: string; // path where to write the package.json - license?: string; - scripts?: Record; - workspaces?: string[]; - componentId?: BitId; - exported?: boolean; - bit?: Record; - - constructor( - componentRootFolder: string, - { - name, - version, - homepage, - main, - dependencies, - devDependencies, - peerDependencies, - license, - scripts, - workspaces, - componentId, - exported, - bit, - }: PackageJsonProps - ) { - this.name = name; - this.version = version; - this.homepage = homepage; - this.main = main; - this.dependencies = dependencies; - this.devDependencies = devDependencies; - this.peerDependencies = peerDependencies; - this.componentRootFolder = componentRootFolder; - this.license = license; - this.scripts = scripts; - this.workspaces = workspaces; - this.componentId = componentId; - this.exported = exported; - this.bit = bit; - } - - static loadSync(componentRootFolder: string): PackageJson | null { - const composedPath = composePath(componentRootFolder); - if (!PackageJson.hasExisting(componentRootFolder)) return null; - try { - const componentJsonObject = fs.readJsonSync(composedPath); - return new PackageJson(componentRootFolder, componentJsonObject); - } catch (err: any) { - logger.error(`failed parsing ${composedPath}, the file content is ${fs.readFileSync(composedPath)}`); - throw err; - } - } - - static hasExisting(componentRootFolder: string): boolean { - const packageJsonPath = composePath(componentRootFolder); - return fs.pathExistsSync(packageJsonPath); - } - - /* - * load package.json from path - */ - static async getPackageJson(pathStr: string) { - const getRawObject = () => fs.readJson(composePath(pathStr)); - const exist = PackageJson.hasExisting(pathStr); - if (exist) return getRawObject(); - return null; - } - - /* - * save package.json in path - */ - static saveRawObject(pathStr: string, obj: Record) { - return fs.outputJSON(composePath(pathStr), obj, { spaces: 2 }); - } - - /* - * remove workspaces dir from workspace in package.json with changing other fields in package.json - */ - static async removeComponentsFromWorkspaces(rootDir: string, pathsTOoRemove: string[]) { - const pkg = (await PackageJson.getPackageJson(rootDir)) || {}; - const workspaces = this.extractWorkspacesPackages(pkg); - if (!workspaces) return; - const updatedWorkspaces = workspaces.filter((folder) => !pathsTOoRemove.includes(folder)); - this.updateWorkspacesPackages(pkg, updatedWorkspaces); - await PackageJson.saveRawObject(rootDir, pkg); - } - - /* - * remove components from package.json dependencies - */ - static async removeComponentsFromDependencies(rootDir: string, components: ConsumerComponent[]) { - const pkg = await PackageJson.getPackageJson(rootDir); - if (pkg && pkg.dependencies) { - components.forEach((c) => { - delete pkg.dependencies[componentIdToPackageName(c)]; - }); - await PackageJson.saveRawObject(rootDir, pkg); - } - } - - static extractWorkspacesPackages(packageJson: { [k: string]: any }): string[] | null { - if (!packageJson.workspaces) return null; - this.throwForInvalidWorkspacesConfig(packageJson); - if (Array.isArray(packageJson.workspaces)) { - return packageJson.workspaces; - } - if (Array.isArray(packageJson.workspaces.packages)) { - return packageJson.workspaces.packages; - } - return null; - } - - static updateWorkspacesPackages(packageJson, workspacesPackages): void { - if (!packageJson.workspaces) return; - this.throwForInvalidWorkspacesConfig(packageJson); - if (Array.isArray(packageJson.workspaces)) { - packageJson.workspaces = workspacesPackages; - } - if (Array.isArray(packageJson.workspaces.packages)) { - packageJson.workspaces.packages = workspacesPackages; - } - } - - /** - * according to Yarn Git repo, the workspaces type configured as the following - * `workspaces?: Array | WorkspacesConfig` - * and `WorkspacesConfig` is: - * `export type WorkspacesConfig = { packages?: Array, nohoist?: Array };` - * see https://github.com/yarnpkg/yarn/blob/master/src/types.js - */ - static throwForInvalidWorkspacesConfig(packageJson) { - if (!packageJson.workspaces) return; - if ( - typeof packageJson.workspaces !== 'object' || - (!Array.isArray(packageJson.workspaces) && !Array.isArray(packageJson.workspaces.packages)) - ) { - throw new Error('workspaces property does not have the correct format, please refer to Yarn documentation'); - } - } -}