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

fix(envs loading) - Improve envs loading on first install #9134

Merged
merged 5 commits into from
Aug 21, 2024
Merged
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
3 changes: 2 additions & 1 deletion e2e/harmony/install.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ describe('install command', function () {
helper.scopeHelper.getClonedLocalScope(wsEmptyNM);
output = helper.command.install(undefined, { 'recurring-install': '' });
});
it('should show a warning that the workspace has old env without env.jsonc but not offer the recurring-install flag', async () => {
// Skip for now, I don't know think it is relevant anymore (the warning is not shown anymore which is expected)
it.skip('should show a warning that the workspace has old env without env.jsonc but not offer the recurring-install flag', async () => {
const msg = stripAnsi(getAnotherInstallRequiredOutput(true, [envId]));
expect(output).to.have.string(msg);
});
Expand Down
13 changes: 11 additions & 2 deletions scopes/compilation/compiler/workspace-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,18 @@ export class WorkspaceCompiler {
}
}

async onAspectLoadFail(err: Error & { code?: string }, id: ComponentID): Promise<boolean> {
async onAspectLoadFail(err: Error & { code?: string }, component: Component): Promise<boolean> {
const id = component.id;
const deps = this.dependencyResolver.getDependencies(component);
const depsIds = deps.getComponentDependencies().map((dep) => {
return dep.id.toString();
});
if (err.code && err.code === 'MODULE_NOT_FOUND' && this.workspace) {
await this.compileComponents([id.toString()], { initiator: CompilationInitiator.AspectLoadFail }, true);
await this.compileComponents(
[id.toString(), ...depsIds],
{ initiator: CompilationInitiator.AspectLoadFail },
true
);
return true;
}
return false;
Expand Down
4 changes: 2 additions & 2 deletions scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export type LoadExtByManifestOptions = {
unifyErrorsByExtId?: boolean;
};

type OnAspectLoadError = (err: Error, id: ComponentID) => Promise<boolean>;
type OnAspectLoadError = (err: Error, component: Component) => Promise<boolean>;
export type OnAspectLoadErrorSlot = SlotRegistry<OnAspectLoadError>;

export type OnAspectLoadErrorHandler = (err: Error, component: Component) => Promise<boolean>;
Expand Down Expand Up @@ -161,7 +161,7 @@ export class AspectLoaderMain {
const entries = this.onAspectLoadErrorSlot.toArray(); // e.g. [ [ 'teambit.bit/compiler', [Function: bound onAspectLoadError] ] ]
let isFixed = false;
await mapSeries(entries, async ([, onAspectFailFunc]) => {
const result = await onAspectFailFunc(err, component.id);
const result = await onAspectFailFunc(err, component);
if (result) isFixed = true;
});

Expand Down
19 changes: 8 additions & 11 deletions scopes/harmony/aspect-loader/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ export class Plugins {
});
aspect.addRuntime({
provider: async () => {
await Promise.all(
plugins.map(async (plugin) => {
return this.registerPluginWithTryCatch(plugin, aspect);
})
);
await Promise.all(plugins.map((plugin) => this.registerPluginWithTryCatch(plugin, aspect)));
// Return an empty object so haromny will have something in the extension instance
// otherwise it will throw an error when trying to access the extension instance (harmony.get)
return {};
Expand All @@ -68,7 +64,6 @@ export class Plugins {
try {
const isModule = isEsmModule(plugin.path);
const module = isModule ? await this.loadModule(plugin.path) : undefined;

if (isModule && !module) {
this.logger.consoleFailure(
`failed to load plugin ${plugin.path}, make sure to use 'export default' to expose your plugin`
Expand All @@ -86,12 +81,14 @@ export class Plugins {
const isFixed = await this.triggerOnAspectLoadError(firstErr, this.component);
let errAfterReLoad;
if (isFixed) {
this.logger.info(
`the loading issue might be fixed now, re-loading plugin with pattern "${
plugin.def.pattern
}", in component ${this.component.id.toString()}`
);
try {
const isModule = isEsmModule(plugin.path);
const module = isModule ? await this.loadModule(plugin.path) : undefined;
this.logger.info(
`the loading issue might be fixed now, re-loading plugin with pattern "${
plugin.def.pattern
}", in component ${this.component.id.toString()}`
);
return plugin.register(aspect, module);
} catch (err: any) {
this.logger.warn(
Expand Down
75 changes: 51 additions & 24 deletions scopes/workspace/install/install.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CompilerMain, CompilerAspect, CompilationInitiator } from '@teambit/com
import { CLIMain, CommandList, CLIAspect, MainRuntime } from '@teambit/cli';
import chalk from 'chalk';
import { WorkspaceAspect, Workspace, ComponentConfigFile } from '@teambit/workspace';
import { compact, mapValues, omit, uniq, intersection } from 'lodash';
import { compact, mapValues, omit, uniq, intersection, groupBy } from 'lodash';
import { ProjectManifest } from '@pnpm/types';
import { GenerateResult, GeneratorAspect, GeneratorMain } from '@teambit/generator';
import { componentIdToPackageName } from '@teambit/pkg.modules.component-package-name';
Expand Down Expand Up @@ -45,6 +45,7 @@ import { WorkspaceConfigFilesAspect, WorkspaceConfigFilesMain } from '@teambit/w
import { Logger, LoggerAspect, LoggerMain } from '@teambit/logger';
import { IssuesAspect, IssuesMain } from '@teambit/issues';
import { snapToSemver } from '@teambit/component-package-version';
import { AspectDefinition, AspectLoaderAspect, AspectLoaderMain } from '@teambit/aspect-loader';
import hash from 'object-hash';
import { DependencyTypeNotSupportedInPolicy } from './exceptions';
import { InstallAspect } from './install.aspect';
Expand All @@ -53,7 +54,6 @@ import { LinkCommand } from './link';
import InstallCmd from './install.cmd';
import UninstallCmd from './uninstall.cmd';
import UpdateCmd from './update.cmd';
import { AspectLoaderAspect, AspectLoaderMain } from '@teambit/aspect-loader';

export type WorkspaceLinkOptions = LinkingOptions & {
rootPolicy?: WorkspacePolicy;
Expand Down Expand Up @@ -106,6 +106,8 @@ type GetComponentsAndManifestsOptions = Omit<
export class InstallMain {
private visitedAspects: Set<string> = new Set();

private oldNonLoadedEnvs: string[] = [];

constructor(
private dependencyResolver: DependencyResolverMain,

Expand Down Expand Up @@ -364,8 +366,10 @@ export class InstallMain {
);
let cacheCleared = false;
await this.linkCodemods(compDirMap);
const oldNonLoadedEnvs = this.setOldNonLoadedEnvs();
await this.reloadMovedEnvs();
await this.reloadNonLoadedEnvs();

const shouldClearCacheOnInstall = this.shouldClearCacheOnInstall();
if (options?.compile ?? true) {
const compileStartTime = process.hrtime();
Expand All @@ -375,7 +379,8 @@ export class InstallMain {
// We need to clear cache before compiling the components or it might compile them with the default env
// incorrectly in case the env was not loaded correctly before the installation.
// We don't want to clear the failed to load envs because we want to show the warning at the end
await this.workspace.clearCache({ skipClearFailedToLoadEnvs: true });
// await this.workspace.clearCache({ skipClearFailedToLoadEnvs: true });
await this.workspace.clearCache();
cacheCleared = true;
}
await this.compiler.compileOnWorkspace([], { initiator: CompilationInitiator.Install });
Expand All @@ -392,7 +397,7 @@ export class InstallMain {
}
if (!dependenciesChanged) break;
if (!options?.recurringInstall) break;
const oldNonLoadedEnvs = this.getOldNonLoadedEnvs();

if (!oldNonLoadedEnvs.length) break;
prevManifests.add(manifestsHash(current.manifests));
// If we run compile we do the clear cache before the compilation so no need to clean it again (it's an expensive
Expand Down Expand Up @@ -482,28 +487,45 @@ export class InstallMain {
// This is a bug in the flow and should be fixed.
// skipDeps: true,
});
const loadedPlugins = compact(
await Promise.all(
aspects.map((aspectDef) => {
const localPath = aspectDef.aspectPath;
const component = aspectDef.component;
if (!component) return undefined;
const plugins = this.aspectLoader.getPlugins(component, localPath);
if (plugins.has()) {
return plugins.load(MainRuntime.name);
}
})
)
);
// This is a very special case which we need to compile our envs before loading them correctly.
const grouped = groupBy(aspects, (aspectDef) => {
return aspectDef.component?.id.toStringWithoutVersion() === 'bitdev.general/envs/bit-env';
});
await this.reloadAspects(grouped.true || []);
const otherEnvs = grouped.false || [];
await Promise.all(
loadedPlugins.map((plugin) => {
const runtime = plugin.getRuntime(MainRuntime);
return runtime?.provider(undefined, undefined, undefined, this.harmony);
otherEnvs.map(async (aspectDef) => {
const id = aspectDef.component?.id;
if (!id) return;
await this.workspace.clearComponentCache(id);
})
);
await this.reloadAspects(grouped.false || []);
}
}

private async reloadAspects(aspects: AspectDefinition[]) {
const loadedPlugins = compact(
await Promise.all(
aspects.map((aspectDef) => {
const localPath = aspectDef.aspectPath;
const component = aspectDef.component;
if (!component) return undefined;
const plugins = this.aspectLoader.getPlugins(component, localPath);
if (plugins.has()) {
return plugins.load(MainRuntime.name);
}
})
)
);
await Promise.all(
loadedPlugins.map((plugin) => {
const runtime = plugin.getRuntime(MainRuntime);
return runtime?.provider(undefined, undefined, undefined, this.harmony);
})
);
}

private async _getComponentsManifestsAndRootPolicy(
installer: DependencyInstaller,
options: GetComponentsAndManifestsOptions & {
Expand Down Expand Up @@ -687,6 +709,14 @@ export class InstallMain {
};
}

public setOldNonLoadedEnvs() {
const nonLoadedEnvs = this.envs.getFailedToLoadEnvs();
const envsWithoutManifest = Array.from(this.dependencyResolver.envsWithoutManifest);
const oldNonLoadedEnvs = intersection(nonLoadedEnvs, envsWithoutManifest);
this.oldNonLoadedEnvs = oldNonLoadedEnvs;
return oldNonLoadedEnvs;
}

/**
* This function returns a list of old non-loaded environments names.
* @returns an array of strings called `oldNonLoadedEnvs`. This array contains the names of environment variables that
Expand All @@ -695,10 +725,7 @@ export class InstallMain {
* correctly
*/
public getOldNonLoadedEnvs() {
const nonLoadedEnvs = this.envs.getFailedToLoadEnvs();
const envsWithoutManifest = Array.from(this.dependencyResolver.envsWithoutManifest);
const oldNonLoadedEnvs = intersection(nonLoadedEnvs, envsWithoutManifest);
return oldNonLoadedEnvs;
return this.oldNonLoadedEnvs;
}

private async _updateRootDirs(rootDirs: string[]) {
Expand Down