Skip to content

Commit

Permalink
feat: install --update (#7085)
Browse files Browse the repository at this point in the history
## Proposed Changes

- `bit install --update` updates all dependencies (direct and indirect).
Existing semver ranges are respected.
- Newly installed dependency are saved in `workspace.jsonc` with the `^`
prefix by default.
  • Loading branch information
zkochan authored Feb 26, 2023
1 parent 83bd8f6 commit 4175e60
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 16 deletions.
2 changes: 1 addition & 1 deletion e2e/harmony/deduplication.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ const get = require("lodash.get");`
});
it("should install the package from the root manifest when the component doesn't have a policy for it", () => {
const comp4Output = helper.command.showComponentParsed('comp4');
expect(comp4Output.packageDependencies['lodash.get']).to.equal('4.4.2');
expect(comp4Output.packageDependencies['lodash.get']).to.equal('^4.4.2');
});
});
});
4 changes: 2 additions & 2 deletions e2e/harmony/import-harmony.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ describe('import functionality on Harmony', function () {

// intermediate step, make sure the types are saved in the
const show = helper.command.showComponentParsed('bar');
expect(show.devPackageDependencies).to.include({ '@types/cors': '2.8.10' });
expect(show.devPackageDependencies).to.include({ '@types/cors': '^2.8.10' });

helper.command.tagAllWithoutBuild();
helper.command.export();
Expand All @@ -303,7 +303,7 @@ describe('import functionality on Harmony', function () {
});
it('bit show should show the typed dependency', () => {
const show = helper.command.showComponentParsed('bar');
expect(show.devPackageDependencies).to.include({ '@types/cors': '2.8.10' });
expect(show.devPackageDependencies).to.include({ '@types/cors': '^2.8.10' });
});
});
});
16 changes: 13 additions & 3 deletions e2e/harmony/imported-component-deps.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { resolveFrom } from '@teambit/toolbox.modules.module-resolver';
import { expect } from 'chai';
import fs from 'fs-extra';
import path from 'path';
import Helper from '../../src/e2e-helper/e2e-helper';
import NpmCiRegistry, { supportNpmCiRegistryTesting } from '../npm-ci-registry';
Expand Down Expand Up @@ -49,10 +51,18 @@ const isPositive = require('is-positive');
).to.eq('0.0.2');
});
it('should install package dependencies from their respective models to the imported components', () => {
expect(helper.fs.readJsonFile(`node_modules/is-positive/package.json`).version).to.eq('1.0.0');
expect(
helper.fs.readJsonFile(
path.join(helper.scopes.remoteWithoutOwner, `comp2/node_modules/is-positive/package.json`)
fs.readJsonSync(
resolveFrom(path.join(helper.fixtures.scopes.localPath, helper.scopes.remoteWithoutOwner, 'comp1'), [
'is-positive/package.json',
])
).version
).to.eq('1.0.0');
expect(
fs.readJsonSync(
resolveFrom(path.join(helper.fixtures.scopes.localPath, helper.scopes.remoteWithoutOwner, 'comp2'), [
'is-positive/package.json',
])
).version
).to.eq('2.0.0');
});
Expand Down
117 changes: 117 additions & 0 deletions e2e/harmony/install.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'path';
import fs from 'fs-extra';
import { addDistTag } from '@pnpm/registry-mock';
import { IssuesClasses } from '@teambit/component-issues';
import { expect } from 'chai';
import Helper from '../../src/e2e-helper/e2e-helper';
Expand Down Expand Up @@ -65,3 +66,119 @@ describe('install command', function () {
});
});
});

(supportNpmCiRegistryTesting ? describe : describe.skip)('install --update', function () {
this.timeout(0);
let helper: Helper;
describe('using pnpm', () => {
let npmCiRegistry: NpmCiRegistry;
before(async () => {
helper = new Helper({ scopesOptions: { remoteScopeWithDot: true } });
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.bitJsonc.setPackageManager(`teambit.dependencies/pnpm`);
npmCiRegistry = new NpmCiRegistry(helper);
await npmCiRegistry.init();

helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl());
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' });
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' });
helper.command.install('@pnpm.e2e/dep-of-pkg-with-1-dep @pnpm.e2e/parent-of-pkg-with-1-dep');
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.1.0', distTag: 'latest' });
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '101.0.0', distTag: 'latest' });
helper.command.install('--update');
});
after(() => {
helper.command.delConfig('registry');
npmCiRegistry.destroy();
helper.scopeHelper.destroy();
});
it('should update direct dependency inside existing range', async () => {
const manifest = fs.readJSONSync(
path.join(helper.fixtures.scopes.localPath, 'node_modules/@pnpm.e2e/dep-of-pkg-with-1-dep/package.json')
);
expect(manifest.version).to.eq('100.1.0');
});
it('should update subdependency inside existing range', async () => {
const dirs = fs.readdirSync(path.join(helper.fixtures.scopes.localPath, 'node_modules/.pnpm'));
expect(dirs).to.include('@[email protected]');
});
});
});

describe('install new dependencies', function () {
this.timeout(0);
let helper: Helper;
let bitJsonc;
describe('using pnpm', () => {
before(() => {
helper = new Helper({ scopesOptions: { remoteScopeWithDot: true } });
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.extensions.bitJsonc.setPackageManager('teambit.dependencies/pnpm');
helper.command.install('is-positive@~1.0.0 [email protected] is-even@1 is-negative');
bitJsonc = helper.bitJsonc.read();
});
after(() => {
helper.scopeHelper.destroy();
});
it('should add new dependency preserving the ~ prefix', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-positive']).to.equal(
'~1.0.0'
);
});
it('should add new dependency with ^ prefix if the dependency was installed by specifying the exact version', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-odd']).to.equal('^1.0.0');
});
it('should add new dependency with ^ prefix if the dependency was installed by specifying a range not using ~', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-even']).to.equal('^1.0.0');
});
it('should add new dependency with ^ prefix by default', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-negative'][0]).to.equal('^');
});
});
describe('using yarn', () => {
before(() => {
helper = new Helper({ scopesOptions: { remoteScopeWithDot: true } });
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.extensions.bitJsonc.setPackageManager('teambit.dependencies/yarn');
helper.command.install('is-positive@~1.0.0 [email protected] is-even@1 is-negative');
bitJsonc = helper.bitJsonc.read();
});
after(() => {
helper.scopeHelper.destroy();
});
it('should add new dependency preserving the ~ prefix', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-positive']).to.equal(
'~1.0.0'
);
});
it('should add new dependency with ^ prefix if the dependency was installed by specifying the exact version', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-odd']).to.equal('^1.0.0');
});
it('should add new dependency with ^ prefix if the dependency was installed by specifying a range not using ~', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-even']).to.equal('^1.0.0');
});
it('should add new dependency with ^ prefix by default', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-negative'][0]).to.equal('^');
});
});
});

describe('named install', function () {
this.timeout(0);
let helper: Helper;
let bitJsonc;
before(() => {
helper = new Helper({ scopesOptions: { remoteScopeWithDot: true } });
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.extensions.bitJsonc.setPackageManager('teambit.dependencies/pnpm');
helper.command.install('[email protected]');
helper.command.install('is-positive');
bitJsonc = helper.bitJsonc.read();
});
after(() => {
helper.scopeHelper.destroy();
});
it('should override already existing dependency with the latest version', () => {
expect(bitJsonc['teambit.dependencies/dependency-resolver'].policy.dependencies['is-positive']).to.equal('^3.1.0');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,21 @@ export class DependencyResolverMain {
}

getSavePrefix(): string {
return this.config.savePrefix || '';
return this.config.savePrefix || '^';
}

getVersionWithSavePrefix(version: string, overridePrefix?: string): string {
getVersionWithSavePrefix({
version,
overridePrefix,
wantedRange,
}: {
version: string;
overridePrefix?: string;
wantedRange?: string;
}): string {
if (wantedRange && ['~', '^'].includes(wantedRange[0])) {
return wantedRange;
}
const prefix = overridePrefix || this.getSavePrefix();
const versionWithPrefix = `${prefix}${version}`;
if (!semver.validRange(versionWithPrefix)) {
Expand Down
3 changes: 3 additions & 0 deletions scopes/dependencies/dependency-resolver/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ export type PackageManagerInstallOptions = {
peerDependencyRules?: PeerDependencyRules;

includeOptionalDeps?: boolean;

updateAll?: boolean;
};

export type PackageManagerGetPeerDependencyIssuesOptions = PackageManagerInstallOptions;

export type ResolvedPackageVersion = {
packageName: string;
version: string | null;
wantedRange?: string;
isSemver: boolean;
resolvedVia?: string;
};
Expand Down
10 changes: 7 additions & 3 deletions scopes/dependencies/pnpm/lynx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export async function install(
proxyConfig: PackageManagerProxyConfig = {},
networkConfig: PackageManagerNetworkConfig = {},
options: {
updateAll?: boolean;
nodeLinker?: 'hoisted' | 'isolated';
overrides?: Record<string, string>;
rootComponents?: boolean;
Expand Down Expand Up @@ -240,6 +241,8 @@ export async function install(
ignoreMissing: ['*'],
...options?.peerDependencyRules,
},
update: options.updateAll,
depth: options.updateAll ? Infinity : 0,
};

const stopReporting = initDefaultReporter({
Expand Down Expand Up @@ -452,17 +455,18 @@ export async function resolveRemoteVersion(
alias: parsedPackage.name,
pref: parsedPackage.version,
};
const isValidRange = parsedPackage.version ? !!semver.validRange(parsedPackage.version) : false;
resolveOpts.registry = registry;
const val = await resolve(wantedDep, resolveOpts);
if (!val.manifest) {
throw new BitError('The resolved package has no manifest');
}
const version = isValidRange ? parsedPackage.version : val.manifest.version;
const wantedRange =
parsedPackage.version && semver.validRange(parsedPackage.version) ? parsedPackage.version : undefined;

return {
packageName: val.manifest.name,
version,
version: val.manifest.version,
wantedRange,
isSemver: true,
resolvedVia: val.resolvedVia,
};
Expand Down
1 change: 1 addition & 0 deletions scopes/dependencies/pnpm/pnpm.package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class PnpmPackageManager implements PackageManager {
sideEffectsCacheRead: installOptions.sideEffectsCache ?? true,
sideEffectsCacheWrite: installOptions.sideEffectsCache ?? true,
pnpmHomeDir: config.pnpmHomeDir,
updateAll: installOptions.updateAll,
},
this.logger
);
Expand Down
2 changes: 2 additions & 0 deletions scopes/dependencies/yarn/yarn.package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ export class YarnPackageManager implements PackageManager {
return {
packageName: parsedPackage.name,
version: parsedVersion,
wantedRange: parsedVersion,
isSemver: true,
};
}
Expand Down Expand Up @@ -478,6 +479,7 @@ export class YarnPackageManager implements PackageManager {
return {
packageName: parsedPackage.name,
version,
wantedRange: parsedVersion,
isSemver: true,
};
}
Expand Down
5 changes: 4 additions & 1 deletion scopes/workspace/install/install.cmd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type InstallCmdOptions = {
skipDedupe: boolean;
skipImport: boolean;
skipCompile: boolean;
update: boolean;
updateExisting: boolean;
savePrefix: string;
addMissingPeers: boolean;
Expand All @@ -29,8 +30,9 @@ export default class InstallCmd implements Command {
options = [
['v', 'variants <variants>', 'add packages to specific variants'],
['t', 'type [lifecycleType]', '"runtime" (default) or "peer" (dev is not a valid option)'],
['u', 'update', 'update all dependencies'],
[
'u',
'',
'update-existing [updateExisting]',
'DEPRECATED (not needed anymore, it is the default now). update existing dependencies version and types',
],
Expand Down Expand Up @@ -74,6 +76,7 @@ export default class InstallCmd implements Command {
addMissingPeers: options.addMissingPeers,
compile: !options.skipCompile,
includeOptionalDeps: !options.noOptional,
updateAll: options.update,
};
const components = await this.install.install(packages, installOpts);
const endTime = Date.now();
Expand Down
11 changes: 7 additions & 4 deletions scopes/workspace/install/install.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type WorkspaceInstallOptions = {
savePrefix?: string;
compile?: boolean;
includeOptionalDeps?: boolean;
updateAll?: boolean;
};

export type ModulesInstallOptions = Omit<WorkspaceInstallOptions, 'updateExisting' | 'lifecycleType' | 'import'>;
Expand Down Expand Up @@ -159,10 +160,11 @@ export class InstallMain {
const newWorkspacePolicyEntries: WorkspacePolicyEntry[] = [];
resolvedPackages.forEach((resolvedPackage) => {
if (resolvedPackage.version) {
const versionWithPrefix = this.dependencyResolver.getVersionWithSavePrefix(
resolvedPackage.version,
options?.savePrefix
);
const versionWithPrefix = this.dependencyResolver.getVersionWithSavePrefix({
version: resolvedPackage.version,
overridePrefix: options?.savePrefix,
wantedRange: resolvedPackage.wantedRange,
});
newWorkspacePolicyEntries.push({
dependencyId: resolvedPackage.packageName,
value: {
Expand Down Expand Up @@ -200,6 +202,7 @@ export class InstallMain {
packageImportMethod: this.dependencyResolver.config.packageImportMethod,
rootComponents: hasRootComponents,
nodeLinker: this.dependencyResolver.nodeLinker(),
updateAll: options?.updateAll,
};
// TODO: pass get install options
const installer = this.dependencyResolver.getInstaller({});
Expand Down

0 comments on commit 4175e60

Please sign in to comment.