Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(envs) - re-implement envs extends feature #9257

Draft
wants to merge 23 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 207 additions & 50 deletions e2e/harmony/dependencies/env-jsonc-policies.e2e.ts

Large diffs are not rendered by default.

37 changes: 24 additions & 13 deletions scopes/component/dev-files/dev-files.main.runtime.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Dependency as LegacyDependency } from '@teambit/legacy/dist/consumer/component/dependencies';
import { SourceFile } from '@teambit/component.sources';
import { MainRuntime } from '@teambit/cli';
import { ScopeAspect, ScopeMain } from '@teambit/scope';
Expand Down Expand Up @@ -68,12 +69,12 @@ export class DevFilesMain {
* computing of dev patterns is a merge of the configuration, the env (env.getDevPatterns(component)) and
* the registering aspects (through registerDevPattern()).
*/
async computeDevPatterns(component: Component) {
async computeDevPatterns(component: Component, envExtendsDeps?: LegacyDependency[]) {
const entry = component.state.aspects.get(DevFilesAspect.id);
const configuredPatterns = entry?.config.devFilePatterns || [];

const fromSlot = await this.computeDevPatternsFromSlot(component);
const fromEnv = await this.computeDevPatternsFromEnv(component, fromSlot.names);
const fromEnv = await this.computeDevPatternsFromEnv(component, fromSlot.names, envExtendsDeps);

const res = Object.assign(
{
Expand Down Expand Up @@ -116,10 +117,12 @@ export class DevFilesMain {

private async computeDevPatternsFromEnv(
component: Component,
patternNames: { [name: string]: string }
patternNames: { [name: string]: string },
envExtendsDeps?: LegacyDependency[]
): Promise<{ [id: string]: string[] }> {
const envId = this.envs.getEnvId(component);
const fromEnvJsonFile = await this.computeDevPatternsFromEnvJsoncFile(envId);
const envId = (await this.envs.getOrCalculateEnvId(component)).toString();
const fromEnvJsonFile = await this.computeDevPatternsFromEnvJsoncFile(envId, undefined, envExtendsDeps);

let fromEnvFunc;
if (!fromEnvJsonFile) {
const envDef = await this.envs.calculateEnv(component, { skipWarnings: !!this.workspace?.inInstallContext });
Expand Down Expand Up @@ -147,16 +150,18 @@ export class DevFilesMain {

private async computeDevPatternsFromEnvJsoncFile(
envId: string,
legacyFiles?: SourceFile[]
legacyFiles?: SourceFile[],
envExtendsDeps?: LegacyDependency[]
): Promise<string[] | undefined> {
const isCoreEnv = this.envs.isCoreEnv(envId);

if (isCoreEnv) return undefined;
let envJsonc;
if (legacyFiles) {
envJsonc = this.envs.getEnvManifest(undefined, legacyFiles);
envJsonc = await this.envs.calculateEnvManifest(undefined, legacyFiles, envExtendsDeps);
} else {
const envComponent = await this.envs.getEnvComponentByEnvId(envId, envId);
envJsonc = this.envs.getEnvManifest(envComponent, undefined);
envJsonc = await this.envs.calculateEnvManifest(envComponent, undefined, envExtendsDeps);
}

if (!envJsonc) return undefined;
Expand Down Expand Up @@ -199,20 +204,26 @@ export class DevFilesMain {
return new DevFiles(rawDevFiles);
}

async getDevFilesForConsumerComp(consumerComponent: LegacyComponent): Promise<string[]> {
async getDevFilesForConsumerComp(
consumerComponent: LegacyComponent,
envExtendsDeps?: LegacyDependency[]
): Promise<string[]> {
const componentId = consumerComponent.id;
// Do not change the storeInCache=false arg. if you think you need to change it, please talk to Gilad first
const component = await this.workspace.get(componentId, consumerComponent, true, false, { loadExtensions: false });
const component = await this.workspace.get(componentId, consumerComponent, true, false, {
loadExtensions: false,
// executeLoadSlot: false,
});
if (!component) throw Error(`failed to transform component ${consumerComponent.id.toString()} in harmony`);
const computedDevFiles = await this.computeDevFiles(component);
const computedDevFiles = await this.computeDevFiles(component, envExtendsDeps);
return computedDevFiles.list();
}

/**
* compute all dev files of a component.
*/
async computeDevFiles(component: Component): Promise<DevFiles> {
const devPatterns = await this.computeDevPatterns(component);
async computeDevFiles(component: Component, envExtendsDeps?: LegacyDependency[]): Promise<DevFiles> {
const devPatterns = await this.computeDevPatterns(component, envExtendsDeps);
const rawDevFiles = Object.keys(devPatterns).reduce((acc, aspectId) => {
if (!acc[aspectId]) acc[aspectId] = [];
const patterns = devPatterns[aspectId];
Expand Down
39 changes: 28 additions & 11 deletions scopes/component/snapping/generate-comp-from-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ import { DependencyResolverMain } from '@teambit/dependency-resolver';
import { FileData } from './snap-from-scope.cmd';
import { SnappingMain, SnapDataParsed } from './snapping.main.runtime';

export type NewDependency = {
id: string; // component-id or package-name. e.g. "teambit.react/react" or "lodash".
version?: string; // version of the package. e.g. "2.0.3". for packages, it is mandatory.
isComponent?: boolean; // default true. if false, it's a package dependency
type?: 'runtime' | 'dev' | 'peer'; // default "runtime".
};

export type NewDependencies = NewDependency[];

export type CompData = {
componentId: ComponentID;
dependencies: string[];
aspects: Record<string, any> | undefined;
message: string | undefined;
files: FileData[] | undefined;
mainFile?: string;
newDependencies?: NewDependencies;
};

/**
Expand All @@ -32,6 +42,7 @@ export type CompData = {
*/
export async function generateCompFromScope(
scope: ScopeMain,
depsResolver: DependencyResolverMain,
compData: CompData,
snapping: SnappingMain
): Promise<Component> {
Expand All @@ -41,14 +52,15 @@ export async function generateCompFromScope(
});
const id = compData.componentId;
const extensions = ExtensionDataList.fromConfigObject(compData.aspects || {});
const { compDeps } = await getCompDeps(scope, depsResolver, compData.newDependencies || []);

const consumerComponent = new ConsumerComponent({
mainFile: compData.mainFile || 'index.ts',
name: compData.componentId.fullName,
scope: compData.componentId.scope,
files,
schema: CURRENT_SCHEMA,
overrides: await ComponentOverrides.loadNewFromScope(id, files, extensions),
overrides: await ComponentOverrides.loadNewFromScope(id, files, extensions, compDeps),
defaultScope: compData.componentId.scope,
extensions,
// the dummy data here are not important. this Version object will be discarded later.
Expand All @@ -73,16 +85,7 @@ export async function generateCompFromScope(
return component[0];
}

export async function addDeps(
component: Component,
snapData: SnapDataParsed,
scope: ScopeMain,
deps: DependenciesMain,
depsResolver: DependencyResolverMain,
snapping: SnappingMain
) {
const newDeps = snapData.newDependencies || [];
const updateDeps = snapData.dependencies || [];
async function getCompDeps(scope: ScopeMain, depsResolver: DependencyResolverMain, newDeps: NewDependencies) {
const compIdsData = newDeps.filter((dep) => dep.isComponent);
const compIdsDataParsed = compIdsData.map((data) => ({
...data,
Expand All @@ -99,7 +102,21 @@ export async function addDeps(
const compDeps = compIdsDataParsed.filter((c) => c.type === 'runtime').map((dep) => toDependency(dep.id));
const compDevDeps = compIdsDataParsed.filter((c) => c.type === 'dev').map((dep) => toDependency(dep.id));
const compPeerDeps = compIdsDataParsed.filter((c) => c.type === 'peer').map((dep) => toDependency(dep.id));
return { compDeps, compDevDeps, compPeerDeps };
}

export async function addDeps(
component: Component,
snapData: SnapDataParsed,
scope: ScopeMain,
deps: DependenciesMain,
depsResolver: DependencyResolverMain,
snapping: SnappingMain
) {
const newDeps = snapData.newDependencies || [];
const updateDeps = snapData.dependencies || [];
const packageDeps = newDeps.filter((dep) => !dep.isComponent);
const { compDeps, compDevDeps, compPeerDeps } = await getCompDeps(scope, depsResolver, newDeps);
const toPackageObj = (pkgs: Array<{ id: string; version?: string }>) => {
return pkgs.reduce((acc, curr) => {
if (!curr.version) throw new Error(`please specify a version for the package dependency: "${curr.id}"`);
Expand Down
8 changes: 2 additions & 6 deletions scopes/component/snapping/snap-from-scope.cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { compact } from 'lodash';
import { Logger } from '@teambit/logger';
import { SnappingMain } from './snapping.main.runtime';
import { BasicTagSnapParams } from './tag-model-component';
import { NewDependencies } from './generate-comp-from-scope';

export type FileData = { path: string; content: string; delete?: boolean };

Expand All @@ -17,12 +18,7 @@ export type SnapDataPerCompRaw = {
files?: FileData[];
isNew?: boolean;
mainFile?: string; // relevant when isNew is true. default to "index.ts".
newDependencies?: Array<{
id: string; // component-id or package-name. e.g. "teambit.react/react" or "lodash".
version?: string; // version of the package. e.g. "2.0.3". for packages, it is mandatory.
isComponent?: boolean; // default true. if false, it's a package dependency
type?: 'runtime' | 'dev' | 'peer'; // default "runtime".
}>;
newDependencies?: NewDependencies;
removeDependencies?: string[];
forkFrom?: string; // origin id to fork from. the componentId is the new id. (no need to populate isNew prop).
version?: string; // relevant when passing "--tag". optionally, specify the semver to tag. default to "patch".
Expand Down
2 changes: 1 addition & 1 deletion scopes/component/snapping/snapping.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ if you're willing to lose the history from the head to the specified version, us
const componentIdsLatest = componentIds.map((id) => id.changeVersion(LATEST));
const newCompsData = compact(snapDataPerComp.map((t) => (t.isNew ? t : null)));
const newComponents = await Promise.all(
newCompsData.map((newComp) => generateCompFromScope(this.scope, newComp, this))
newCompsData.map((newComp) => generateCompFromScope(this.scope, this.dependencyResolver, newComp, this))
);

await this.scope.import(componentIdsLatest, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { PackageJsonFile } from '@teambit/component.sources';
import { PathLinux, resolvePackagePath } from '@teambit/legacy.utils';
import { ResolvedPackageData, resolvePackageData } from '../resolve-pkg-data';
import { Workspace } from '@teambit/workspace';
import { Dependency } from '@teambit/legacy/dist/consumer/component/dependencies';
import { Dependency, Dependencies } from '@teambit/legacy/dist/consumer/component/dependencies';
import { DependencyResolverMain } from '@teambit/dependency-resolver';
import Consumer from '@teambit/legacy/dist/consumer/consumer';
import { ComponentMap } from '@teambit/legacy.bit-map';
import { Logger } from '@teambit/logger';
import ComponentOverrides from '@teambit/legacy/dist/consumer/config/component-overrides';
import OverridesDependencies from './overrides-dependencies';
import { DependenciesData } from './dependencies-data';
import { DebugDependencies, FileType } from './auto-detect-deps';
import { Logger } from '@teambit/logger';

export type AllDependencies = {
dependencies: Dependency[];
Expand Down Expand Up @@ -77,6 +78,26 @@ export class ApplyOverrides {
this.debugDependenciesData = { components: [] };
}

private async setOverridesDependencies() {
const overrides = await this.getOverridesData();
this.component.overrides = overrides;
}

private getEnvExtendsDeps() {
const wsDeps = this.allDependencies.dependencies || [];
const modelDeps = this.component.componentFromModel?.dependencies.dependencies || [];
const merged = Dependencies.merge([wsDeps, modelDeps]);
return merged.get();
}

private async getOverridesData() {
if (this.component.overrides) return this.component.overrides;

const overrides = await ComponentOverrides.loadFromConsumer(this.component, this.getEnvExtendsDeps());

return overrides;
}

get consumer(): Consumer | undefined {
return this.workspace?.consumer;
}
Expand All @@ -86,6 +107,7 @@ export class ApplyOverrides {
overridesDependencies: OverridesDependencies;
autoDetectOverrides?: Record<string, any>;
}> {
await this.setOverridesDependencies();
await this.populateDependencies();
const dependenciesData = new DependenciesData(
this.allDependencies,
Expand Down Expand Up @@ -188,7 +210,8 @@ export class ApplyOverrides {
this.autoDetectOverrides = await this.workspace?.getAutoDetectOverrides(
this.component.extensions,
this.component.id,
this.component.files
this.component.files,
this.getEnvExtendsDeps()
);
}

Expand Down Expand Up @@ -582,7 +605,11 @@ export class ApplyOverrides {
}

private async applyAutoDetectedPeersFromEnvOnEnvItSelf(): Promise<void> {
const envPolicy = await this.depsResolver.getEnvPolicyFromEnvId(this.component.id, this.component.files);
const envPolicy = await this.depsResolver.getEnvPolicyFromEnvId(
this.component.id,
this.component.files,
this.getEnvExtendsDeps()
);
if (!envPolicy) return;
const envPolicyManifest = envPolicy.selfPolicy.toVersionManifest();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ export class AutoDetectDeps {
// we have the files dependencies, these files should be components that are registered in bit.map. Otherwise,
// they are referred as "untracked components" and the user should add them later on in order to tag
this.setTree(dependenciesTree.tree);
const devFiles = await this.devFiles.getDevFilesForConsumerComp(this.component);
if (dependenciesTree.tree['env.jsonc']?.components.length > 0) {
await this.populateDependencies(['env.jsonc'], []);
}
const envExtendsDeps = this.allDependencies.dependencies.length
? this.allDependencies.dependencies
: this.component.componentFromModel?.dependencies.dependencies;
const devFiles = await this.devFiles.getDevFilesForConsumerComp(this.component, envExtendsDeps);
await this.populateDependencies(allFiles, devFiles);
return {
dependenciesData: new DependenciesData(
Expand Down Expand Up @@ -387,6 +393,9 @@ export class AutoDetectDeps {
componentIdResolvedFrom: 'DependencyPkgJson',
packageName: compDep.name,
};
if (originFile === 'env.jsonc') {
depDebug.importSource = 'env.jsonc';
}
const getVersionFromPkgJson = (): string | null => {
const versionFromDependencyPkgJson = getValidVersion(compDep.concreteVersion);
if (versionFromDependencyPkgJson) {
Expand Down
Loading