Skip to content
Open
4 changes: 2 additions & 2 deletions contrib/claude-skill-bit-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ Subcommands: list, get, set, unset, replace, update
scope <sub-command> - manage component scope names and assignments
Subcommands: set, trust, rename, rename-owner, fork
eject-conf <pattern> - create component.json configuration files for components
local-only <sub-command> - manage components that exist only in the workspace
Subcommands: set, unset, list
aspect <sub-command> - manage component aspects and their configurations
Subcommands: list, list-core, get, set, unset, update
local-only <sub-command> - manage components that exist only in the workspace
Subcommands: set, unset, list

Collaboration & Remote
remote - manage remote scopes for self-hosted environments
Expand Down
3 changes: 3 additions & 0 deletions scopes/component/dev-files/dev-files.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export class DevFilesMain {
const isCoreEnv = this.envs.isCoreEnv(envId);

if (isCoreEnv) return undefined;
// envs that used to be core aspects are old-style envs (no env.jsonc). avoid fetching the
// env component just to find out it has no env.jsonc file.
if (this.envs.isLegacyCoreEnv(envId.split('@')[0])) return undefined;
let envJsonc;
if (legacyFiles) {
envJsonc = await this.envs.calculateEnvManifest(undefined, legacyFiles, envExtendsDeps);
Expand Down
10 changes: 9 additions & 1 deletion scopes/dependencies/dependency-resolver/dependency-linker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,15 @@ export class DependencyLinker {
}
const isDistDirExist = fs.pathExistsSync(distDir);
if (!isDistDirExist) {
const newDir = getDistDirForDevEnv(packageName);
let newDir: string;
try {
newDir = getDistDirForDevEnv(packageName);
} catch (err: any) {
// the package may not exist at all (e.g. @teambit/legacy was removed from the repo).
// this link is best-effort for backward compatibility, skip it rather than failing.
this.logger.debug(`linkNonAspectCorePackages, unable to resolve ${packageName}, skipping the link. ${err}`);
return undefined;
}
return { packageName, from: newDir, to: target };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,14 @@ export class DependencyResolverMain {
// in that case we return the dir from the root node_modules
return this.getModulePath(component);
}
const dirInEnvRoot = join(this.getComponentDirInBitRoots(component, options), 'node_modules', pkgName);
if (fs.pathExistsSync(dirInEnvRoot)) return dirInEnvRoot;
let dirInEnvRoot: string | undefined;
try {
dirInEnvRoot = join(this.getComponentDirInBitRoots(component, options), 'node_modules', pkgName);
} catch {
// the component env couldn't be determined (e.g. a component loaded from the scope before
// its extensions were calculated). fall back to the root node_modules.
}
if (dirInEnvRoot && fs.pathExistsSync(dirInEnvRoot)) return dirInEnvRoot;
return this.getModulePath(component);
}

Expand Down Expand Up @@ -1171,7 +1177,10 @@ export class DependencyResolverMain {
const allPoliciesFromEnv = EnvPolicy.fromConfigObject(
policy,
{
includeLegacyPeersInSelfPolicy: envComponent && this.envs.isCoreEnv(envComponent.id.toStringWithoutVersion()),
includeLegacyPeersInSelfPolicy:
envComponent &&
(this.envs.isCoreEnv(envComponent.id.toStringWithoutVersion()) ||
this.envs.isLegacyCoreEnv(envComponent.id.toStringWithoutVersion())),
},
envId
);
Expand Down Expand Up @@ -1212,6 +1221,9 @@ export class DependencyResolverMain {
): Promise<EnvPolicy | undefined> {
const isCoreEnv = this.envs.isCoreEnv(envId);
if (isCoreEnv) return undefined;
// envs that used to be core aspects are old-style envs (no env.jsonc). avoid fetching the
// env component just to find out it has no env.jsonc file.
if (this.envs.isLegacyCoreEnv(envId.split('@')[0])) return undefined;
if (legacyFiles) {
const envJsonc = legacyFiles.find((file) => file.basename === 'env.jsonc');
if (envJsonc) {
Expand All @@ -1230,7 +1242,10 @@ export class DependencyResolverMain {
const idWithoutVersion = options.envId.split('@')[0];
const allPoliciesFromEnv = EnvPolicy.fromConfigObject(
policiesFromEnvConfig,
{ includeLegacyPeersInSelfPolicy: this.envs.isCoreEnv(idWithoutVersion) },
{
includeLegacyPeersInSelfPolicy:
this.envs.isCoreEnv(idWithoutVersion) || this.envs.isLegacyCoreEnv(idWithoutVersion),
},
idWithoutVersion
);
return allPoliciesFromEnv;
Expand Down Expand Up @@ -1391,6 +1406,17 @@ export class DependencyResolverMain {
if (!dep.id) return;
// In case of core aspect, do not update the version, as it's loaded to harmony without version
if (this.aspectLoader.isCoreAspect(dep.id)) return;
// envs that used to be core aspects keep the old single-instance behavior: bind the
// dependency to the already-loaded instance (or the workspace component) instead of
// loading another copy of it from the dependency closure.
const depIdWithoutVersion = dep.id.split('@')[0];
if (this.envs.isLegacyCoreEnv(depIdWithoutVersion)) {
const canonicalId = this.getCanonicalLegacyCoreEnvId(depIdWithoutVersion);
if (canonicalId) {
dep.id = canonicalId;
return;
}
}
// Lazily get the parent component
if (typeof parentComponent === 'string') {
const parentComponentId = await this.componentAspect.getHost().resolveComponentId(parentComponent);
Expand Down Expand Up @@ -1430,6 +1456,20 @@ export class DependencyResolverMain {
return manifest;
}

/**
* for envs that used to be core aspects: the id of the single instance that should be used -
* the already-loaded one (any version) or the workspace component. undefined when neither exists.
*/
private getCanonicalLegacyCoreEnvId(idWithoutVersion: string): string | undefined {
const loadedId = this.aspectLoader.getLoadedAspectIdIgnoringVersion(idWithoutVersion);
if (loadedId) return loadedId;
const host = this.componentAspect.getHost();
const hostWithGetIdIfExist = host as { getIdIfExist?: (id: ComponentID) => ComponentID | undefined };
if (!hostWithGetIdIfExist?.getIdIfExist) return undefined;
const workspaceId = hostWithGetIdIfExist.getIdIfExist(ComponentID.fromString(idWithoutVersion));
return workspaceId?.toString();
}

validateAspectData(data: DependencyResolverComponentData) {
const errorPrefix = `failed validating ${DependencyResolverAspect.id} aspect-data.`;
const allowedPrefixes = ['https://', 'git:', 'git+ssh://', 'git+https://'];
Expand Down Expand Up @@ -1891,6 +1931,22 @@ as an alternative, you can use "+" to keep the same version installed in the wor
});
};
ExtensionDataList.toModelObjectsHook.push(serializeDepResolverDataBeforePersist);

// validate aspects data (env id, dependencies data) before persisting them into the model during snap/tag.
// (was registered by teambit.harmony/aspect before it was removed from the core aspects)
ExtensionDataList.validateBeforePersistHook = (extensionDataList: ExtensionDataList) => {
const envExt = extensionDataList.findCoreExtension(EnvsAspect.id);
if (envExt) {
const result = envs.validateEnvId(envExt);
if (result) return result;
}
const depResolverExt = extensionDataList.findCoreExtension(DependencyResolverAspect.id);
if (depResolverExt) {
const result = dependencyResolver.validateAspectData(depResolverExt.data as any);
if (result) return result;
}
return undefined;
};
PackageJsonTransformer.registerPackageJsonTransformer(async (component, packageJsonObject) => {
const deps = dependencyResolver.getDependencies(component);
const { optionalDependencies, peerDependenciesMeta } = deps.toDependenciesManifest();
Expand Down
Loading