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

feat(snap-from-scope), introduce "forkFrom" prop to fork a remote component and snap from scope #9256

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
50 changes: 50 additions & 0 deletions e2e/harmony/snap-from-scope.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,4 +646,54 @@ export const BasicIdInput = () => {
expect(catComp1.extensions[0].extensionId.version).to.not.equal('0.0.1');
});
});
describe('_snap with forkFrom prop', () => {
let bareTag;
// let beforeSnappingOnScope: string;
before(() => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.fixtures.populateComponents(3);
helper.command.snapAllComponents();
helper.command.export();

bareTag = helper.scopeHelper.getNewBareScope('-bare-merge');
helper.scopeHelper.addRemoteScope(helper.scopes.remotePath, bareTag.scopePath);
// beforeSnappingOnScope = helper.scopeHelper.cloneScope(bareTag.scopePath);
});
describe('snapping them all at the same time without dependencies changes', () => {
let data;
before(() => {
data = [
{
componentId: `${helper.scopes.remote}/comp1-a`,
forkFrom: `${helper.scopes.remote}/comp1`,
message: `msg for first comp`,
},
{
componentId: `${helper.scopes.remote}/comp2-b`,
forkFrom: `${helper.scopes.remote}/comp2`,
message: `msg for second comp`,
},
{
componentId: `${helper.scopes.remote}/comp3-c`,
forkFrom: `${helper.scopes.remote}/comp3`,
message: `msg for third comp`,
},
];
// console.log('command', `bit _snap '${JSON.stringify(data)}'`);
helper.command.snapFromScope(bareTag.scopePath, data);
});
it('should save the components and dependencies according to the new ids', () => {
const comp1Head = helper.command.getHead(`${helper.scopes.remote}/comp1-a`, bareTag.scopePath);
const comp1OnBare = helper.command.catObject(comp1Head, true, bareTag.scopePath);
expect(comp1OnBare.log.message).to.equal('msg for first comp');
expect(comp1OnBare.dependencies[0].id.name).to.equal('comp2-b');
expect(comp1OnBare.dependencies[0].packageName).to.equal(`@${helper.scopes.remote}/comp2-b`);
const flattenedDepNames = comp1OnBare.flattenedDependencies.map((d) => d.name);
expect(flattenedDepNames).to.include('comp2-b');
expect(flattenedDepNames).to.include('comp3-c');
expect(flattenedDepNames).to.not.include('comp2');
expect(flattenedDepNames).to.not.include('comp3');
});
});
});
});
23 changes: 19 additions & 4 deletions scopes/component/forking/forking.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ForkingAspect } from './forking.aspect';
import { ForkingFragment } from './forking.fragment';
import { forkingSchema } from './forking.graphql';
import { ScopeForkCmd, ScopeForkOptions } from './scope-fork.cmd';
import { ScopeAspect, ScopeMain } from '@teambit/scope';

export type ForkInfo = {
forkedFrom: ComponentID;
Expand Down Expand Up @@ -51,6 +52,7 @@ type MultipleForkOptions = {
export class ForkingMain {
constructor(
private workspace: Workspace,
private scope: ScopeMain,
private install: InstallMain,
private dependencyResolver: DependencyResolverMain,
private newComponentHelper: NewComponentHelperMain,
Expand Down Expand Up @@ -253,7 +255,7 @@ export class ForkingMain {
return targetCompId;
}

private async forkRemoteComponent(
async forkRemoteComponent(
sourceId: ComponentID,
targetId?: string,
options?: ForkOptions,
Expand All @@ -268,7 +270,7 @@ the reason is that the refactor changes the components using ${sourceId.toString
}
const targetName = targetId || sourceId.fullName;
const targetCompId = this.newComponentHelper.getNewComponentId(targetName, undefined, options?.scope);
const component = await this.workspace.scope.getRemoteComponent(sourceId);
const component = await this.scope.getRemoteComponent(sourceId);
await this.refactoring.replaceMultipleStrings(
[component],
[
Expand All @@ -286,7 +288,9 @@ the reason is that the refactor changes the components using ${sourceId.toString
this.refactoring.refactorFilenames(component, sourceId, targetCompId);
}
const config = await this.getConfig(component, options);
await this.newComponentHelper.writeAndAddNewComp(component, targetCompId, options, config);
if (this.workspace) {
await this.newComponentHelper.writeAndAddNewComp(component, targetCompId, options, config);
}

return { targetCompId, component };
}
Expand Down Expand Up @@ -353,6 +357,7 @@ the reason is that the refactor changes the components using ${sourceId.toString
static dependencies = [
CLIAspect,
WorkspaceAspect,
ScopeAspect,
DependencyResolverAspect,
ComponentAspect,
NewComponentHelperAspect,
Expand All @@ -365,6 +370,7 @@ the reason is that the refactor changes the components using ${sourceId.toString
static async provider([
cli,
workspace,
scope,
dependencyResolver,
componentMain,
newComponentHelper,
Expand All @@ -375,6 +381,7 @@ the reason is that the refactor changes the components using ${sourceId.toString
]: [
CLIMain,
Workspace,
ScopeMain,
DependencyResolverMain,
ComponentMain,
NewComponentHelperMain,
Expand All @@ -383,7 +390,15 @@ the reason is that the refactor changes the components using ${sourceId.toString
PkgMain,
InstallMain,
]) {
const forkingMain = new ForkingMain(workspace, install, dependencyResolver, newComponentHelper, refactoring, pkg);
const forkingMain = new ForkingMain(
workspace,
scope,
install,
dependencyResolver,
newComponentHelper,
refactoring,
pkg
);
cli.register(new ForkCmd(forkingMain));
graphql.register(forkingSchema(forkingMain));
componentMain.registerShowFragments([new ForkingFragment(forkingMain)]);
Expand Down
44 changes: 43 additions & 1 deletion scopes/component/snapping/generate-comp-from-scope.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComponentID } from '@teambit/component-id';
import ConsumerComponent from '@teambit/legacy/dist/consumer/component';
import { Dependency } from '@teambit/legacy/dist/consumer/component/dependencies';
import { Dependencies, Dependency } from '@teambit/legacy/dist/consumer/component/dependencies';
import { SourceFile } from '@teambit/component.sources';
import { ScopeMain } from '@teambit/scope';
import ComponentOverrides from '@teambit/legacy/dist/consumer/config/component-overrides';
Expand Down Expand Up @@ -162,3 +162,45 @@ export async function addDeps(

await snapping.UpdateDepsAspectsSaveIntoDepsResolver(component, updateDeps);
}

export async function replaceDeps(
comp: Component,
depsResolver: DependencyResolverMain,
scope: ScopeMain,
forkedComps: Component[],
snapDataPerComp: SnapDataParsed[]
) {
const consumerComponent = comp.state._consumer as ConsumerComponent;
const toDependency = (depId: ComponentID) => {
const depComp = forkedComps.find((c) => c.id.isEqualWithoutVersion(depId));
if (!depComp) throw new Error(`unable to find the specified dependency ${depId.toString()} in the scope`);
const pkgName = depsResolver.calcPackageName(depComp);
return new Dependency(depComp.id, [], pkgName);
};
const replace = (deps: Dependencies): Dependencies => {
const dependencies = deps.get().map((dep) => {
const found = snapDataPerComp.find((d) => d.forkFrom && d.forkFrom.isEqualWithoutVersion(dep.id));
if (found) {
return toDependency(found.componentId);
}
return dep;
});
return new Dependencies(dependencies);
};
consumerComponent.dependencies = replace(consumerComponent.dependencies);
consumerComponent.devDependencies = replace(consumerComponent.devDependencies);
consumerComponent.peerDependencies = replace(consumerComponent.peerDependencies);

// replace extensions ids
consumerComponent.extensions.forEach((ext) => {
const extId = ext.extensionId;
if (!extId) return;
const found = snapDataPerComp.find((d) => d.forkFrom && d.forkFrom.isEqualWithoutVersion(extId));
if (found) {
ext.extensionId = found.componentId;
if (ext.newExtensionId) ext.newExtensionId = ext.extensionId;
}
});

comp.state.aspects = await scope.createAspectListFromExtensionDataList(consumerComponent.extensions);
}
2 changes: 2 additions & 0 deletions scopes/component/snapping/snap-from-scope.cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type SnapDataPerCompRaw = {
type?: 'runtime' | 'dev' | 'peer'; // default "runtime".
}>;
removeDependencies?: string[];
forkFrom?: string; // origin id to fork from. the componentId is the new id. (no need to populate isNew prop).
};

type SnapFromScopeOptions = {
Expand Down Expand Up @@ -53,6 +54,7 @@ the input data is a stringified JSON of an array of the following object.
type?: 'runtime' | 'dev' | 'peer'; // default "runtime".
}>;
removeDependencies?: string[]; // component-id (for components) or package-name (for packages) to remove from the dependencies.
forkFrom?: string; // origin id to fork from. the componentId is the new id. (no need to populate isNew prop).
}
an example of the final data: '[{"componentId":"ci.remote2/comp-b","message": "first snap"}]'
`;
Expand Down
53 changes: 46 additions & 7 deletions scopes/component/snapping/snapping.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import ResetCmd from './reset-cmd';
import { tagModelComponent, updateComponentsVersions, BasicTagParams, BasicTagSnapParams } from './tag-model-component';
import { TagDataPerCompRaw, TagFromScopeCmd } from './tag-from-scope.cmd';
import { SnapDataPerCompRaw, SnapFromScopeCmd, FileData } from './snap-from-scope.cmd';
import { addDeps, generateCompFromScope } from './generate-comp-from-scope';
import { addDeps, generateCompFromScope, replaceDeps } from './generate-comp-from-scope';
import { FlattenedEdgesGetter } from './flattened-edges';
import { SnapDistanceCmd } from './snap-distance-cmd';
import {
Expand All @@ -62,6 +62,7 @@ import {
import { ApplicationAspect, ApplicationMain } from '@teambit/application';
import { LaneNotFound } from '@teambit/legacy.scope-api';
import { createLaneInScope } from '@teambit/lanes.modules.create-lane';
import { ForkingAspect, ForkingMain } from '@teambit/forking';

export type TagDataPerComp = {
componentId: ComponentID;
Expand All @@ -86,6 +87,7 @@ export type SnapDataParsed = {
type: 'runtime' | 'dev' | 'peer';
}[];
removeDependencies?: string[];
forkFrom?: ComponentID;
};

export type SnapResults = BasicTagResults & {
Expand Down Expand Up @@ -127,7 +129,8 @@ export class SnappingMain {
private builder: BuilderMain,
private importer: ImporterMain,
private deps: DependenciesMain,
private application: ApplicationMain
private application: ApplicationMain,
private forking: ForkingMain
) {
this.objectsRepo = this.scope?.legacyScope?.objects;
}
Expand Down Expand Up @@ -427,18 +430,46 @@ if you're willing to lose the history from the head to the specified version, us
type: dep.type ?? 'runtime',
})),
removeDependencies: snapData.removeDependencies,
forkFrom: snapData.forkFrom ? ComponentID.fromString(snapData.forkFrom) : undefined,
};
});

// console.log('snapDataPerComp', JSON.stringify(snapDataPerComp, undefined, 2));

const componentIds = compact(snapDataPerComp.map((t) => (t.isNew ? null : t.componentId)));
const componentIds = compact(snapDataPerComp.map((t) => (t.isNew || t.forkFrom ? null : t.componentId)));
const allCompIds = snapDataPerComp.map((s) => s.componentId);
const componentIdsLatest = componentIds.map((id) => id.changeVersion(LATEST));
const newCompsData = compact(snapDataPerComp.map((t) => (t.isNew ? t : null)));
const newCompsData = compact(snapDataPerComp.map((t) => (t.isNew && !t.forkFrom ? t : null)));
const forkedFromData = compact(snapDataPerComp.map((t) => (t.forkFrom ? t : null)));
const newComponents = await Promise.all(
newCompsData.map((newComp) => generateCompFromScope(this.scope, newComp, this))
);
const newForkedComponents = await Promise.all(
forkedFromData.map(async ({ componentId, forkFrom }) => {
if (!forkFrom) throw new Error(`expected to have forkFrom data`);
const results = await this.forking.forkRemoteComponent(forkFrom, componentId.fullName, {
scope: componentId.scope,
});
const originComp = results.component.state._consumer as ConsumerComponent;
const consumerComp = originComp.clone();
consumerComp.name = componentId.fullName;
consumerComp.scope = undefined;
consumerComp.defaultScope = componentId.scope;
consumerComp.version = undefined;

const { version, files: filesBitObject } =
await this.scope.legacyScope.sources.consumerComponentToVersion(consumerComp);
const modelComponent = await this.scope.legacyScope.sources.findOrAddComponent(consumerComp);
consumerComp.version = version.hash().toString();
await this.scope.legacyScope.objects.writeObjectsToTheFS([
version,
modelComponent,
...filesBitObject.map((f) => f.file),
]);
const comp = this.scope.getFromConsumerComponent(consumerComp);
return comp;
})
);

await this.scope.import(componentIdsLatest, {
preferDependencyGraph: false,
Expand Down Expand Up @@ -468,7 +499,11 @@ if you're willing to lose the history from the head to the specified version, us
});
}

const components = [...existingComponents, ...newComponents];
const components = [...existingComponents, ...newComponents, ...newForkedComponents];

await pMapSeries(newForkedComponents, async (comp) => {
await replaceDeps(comp, this.dependencyResolver, this.scope, newForkedComponents, snapDataPerComp);
});

// this must be done before we load component aspects later on, because this updated deps may update aspects.
await pMapSeries(components, async (component) => {
Expand All @@ -479,7 +514,7 @@ if you're willing to lose the history from the head to the specified version, us

// for new components these are not needed. coz when generating them we already add the aspects and the files.
await Promise.all(
existingComponents.map(async (comp) => {
[...existingComponents, ...newForkedComponents].map(async (comp) => {
const snapData = getSnapData(comp.id);
if (snapData.aspects) await this.scope.addAspectsFromConfigObject(comp, snapData.aspects);
if (snapData.files?.length) {
Expand Down Expand Up @@ -1294,6 +1329,7 @@ another option, in case this dependency is not in main yet is to remove all refe
GlobalConfigAspect,
DependenciesAspect,
ApplicationAspect,
ForkingAspect,
];
static runtime = MainRuntime;
static async provider([
Expand All @@ -1310,6 +1346,7 @@ another option, in case this dependency is not in main yet is to remove all refe
globalConfig,
deps,
application,
forking,
]: [
Workspace,
CLIMain,
Expand All @@ -1324,6 +1361,7 @@ another option, in case this dependency is not in main yet is to remove all refe
GlobalConfigMain,
DependenciesMain,
ApplicationMain,
ForkingMain,
]) {
const logger = loggerMain.createLogger(SnappingAspect.id);
const snapping = new SnappingMain(
Expand All @@ -1337,7 +1375,8 @@ another option, in case this dependency is not in main yet is to remove all refe
builder,
importer,
deps,
application
application,
forking
);
const snapCmd = new SnapCmd(snapping, logger, globalConfig);
const tagCmd = new TagCmd(snapping, logger, globalConfig);
Expand Down