Skip to content

Commit

Permalink
feat(version): add --independent-subpackages option, closes #491 (#495
Browse files Browse the repository at this point in the history
)

* feat(version): add `--independent-subpackages` option, closes #491
  • Loading branch information
ghiscoding authored Mar 2, 2023
1 parent 84fafa8 commit dfd0a78
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 20 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"preview:watch:files": "lerna watch --glob=\"src/**/*.ts\" --scope=@lerna-lite/listable --include-dependents -- cross-env-shell echo $LERNA_FILE_CHANGES in package $LERNA_PACKAGE_NAME\"",
"preview:publish-alpha-dry-run": "lerna publish --dry-run --exact --include-merged-tags --preid alpha --dist-tag next prerelease",
"preview:publish": "lerna publish from-package --dry-run",
"preview:version": "lerna version --dry-run",
"preview:version": "lerna version --dry-run --independent-subpackages",
"preview:roll-new-release": "pnpm build && pnpm new-version --dry-run && pnpm new-publish --dry-run",
"new-version": "lerna version",
"new-publish": "lerna publish from-package",
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/schemas/lerna-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,9 @@
"exact": {
"$ref": "#/$defs/commandOptions/shared/exact"
},
"independentSubpackages": {
"$ref": "#/$defs/commandOptions/shared/independentSubpackages"
},
"forcePublish": {
"$ref": "#/$defs/commandOptions/shared/forcePublish"
},
Expand Down Expand Up @@ -1531,6 +1534,10 @@
"type": "boolean",
"description": "During `lerna add`, save the exact version of the newly added package. During `lerna version`, specify cross-dependency version numbers exactly rather than with a caret (^)."
},
"independentSubpackages": {
"type": "boolean",
"description": "Exclude sub-packages when versioning"
},
"ignorePrepublish": {
"type": "boolean",
"description": "During `lerna publish`, when true, disable deprecated 'prepublish' lifecycle script."
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/cli-commands/cli-version-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ export default {
describe: 'Specify cross-dependency version numbers exactly rather than with a caret (^).',
type: 'boolean',
},
'independent-subpackages': {
describe: 'Exclude sub-packages when versioning',
type: 'boolean',
},
'force-publish': {
describe: 'Always include targeted packages in versioning operations, skipping default logic.',
// type must remain ambiguous because it is overloaded (boolean _or_ string _or_ array)
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/models/command-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ export interface VersionCommandOption {
/** Specify cross-dependency version numbers exactly rather than with a caret (^). */
exact?: boolean;

/** optionally exclude sub-packages when versioning */
independentSubpackages?: boolean;

/** Always include targeted packages in versioning operations, skipping default logic. */
forcePublish?: boolean | string;

Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ export interface UpdateCollectorOptions {
conventionalCommits?: boolean;
conventionalGraduate?: boolean | string;
excludeDependents?: boolean;

/** optionally exclude sub-packages when versioning */
independentSubpackages?: boolean;
}

export type RemoteClientType = 'gitlab' | 'github';
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import globby from 'globby';
import { Package } from '../../../package';

jest.mock('../../describe-ref');
Expand Down Expand Up @@ -72,7 +73,9 @@ describe('collectUpdates()', () => {
]);
expect(hasTags).toHaveBeenLastCalledWith(execOpts, '');
expect(describeRefSync).toHaveBeenLastCalledWith(execOpts, undefined, false);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('v1.0.0', execOpts, undefined);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('v1.0.0', execOpts, undefined, {
independentSubpackages: undefined,
});
});

it('returns node with changes in independent mode', () => {
Expand All @@ -93,7 +96,9 @@ describe('collectUpdates()', () => {
]);
expect(hasTags).toHaveBeenLastCalledWith(execOpts, '*@*');
expect(describeRefSync).toHaveBeenLastCalledWith(execOpts, undefined, false);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('v1.0.0', execOpts, undefined);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('v1.0.0', execOpts, undefined, {
independentSubpackages: undefined,
});
});

it('returns changed node and their dependents', () => {
Expand Down Expand Up @@ -363,7 +368,9 @@ describe('collectUpdates()', () => {
expect.objectContaining({ name: 'package-dag-2a' }),
expect.objectContaining({ name: 'package-dag-3' }),
]);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('deadbeef^..deadbeef', execOpts, undefined);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('deadbeef^..deadbeef', execOpts, undefined, {
independentSubpackages: undefined,
});
});

it('uses revision provided by --since <ref>', () => {
Expand All @@ -375,7 +382,9 @@ describe('collectUpdates()', () => {
since: 'beefcafe',
});

expect(makeDiffPredicate).toHaveBeenLastCalledWith('beefcafe', execOpts, undefined);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('beefcafe', execOpts, undefined, {
independentSubpackages: undefined,
});
});

it('does not exit early on tagged release when --since <ref> is passed', () => {
Expand Down Expand Up @@ -410,6 +419,23 @@ describe('collectUpdates()', () => {
ignoreChanges: ['**/README.md'],
});

expect(makeDiffPredicate).toHaveBeenLastCalledWith('v1.0.0', execOpts, ['**/README.md']);
expect(makeDiffPredicate).toHaveBeenLastCalledWith('v1.0.0', execOpts, ['**/README.md'], {
independentSubpackages: undefined,
});
});

it('excludes packages when --independent-subpackages option is enabled', () => {
jest.spyOn(globby, 'sync').mockImplementationOnce(() => ['packages/pkg-2/and-another-thing/package.json']);
const graph = buildGraph();
const pkgs = graph.rawPackageList;
const execOpts = { cwd: '/test' };

collectUpdates(pkgs, graph, execOpts, {
independentSubpackages: true,
});

expect(makeDiffPredicate).toHaveBeenLastCalledWith('v1.0.0', execOpts, undefined, {
independentSubpackages: true,
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
jest.mock('../../../child-process');

import globby from 'globby';

// mocked modules
import * as childProcesses from '../../../child-process';

Expand All @@ -18,7 +20,7 @@ test('git diff call', () => {
'packages/pkg-1/README.md',
]);

const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' });
const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, undefined, {});
const result = hasDiff({
location: '/test/packages/pkg-1',
});
Expand All @@ -34,7 +36,7 @@ test('git diff call', () => {
test('empty diff', () => {
setup('');

const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' });
const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, undefined, {});
const result = hasDiff({
location: '/test/packages/pkg-1',
});
Expand All @@ -45,7 +47,7 @@ test('empty diff', () => {
test('rooted package', () => {
setup('package.json');

const hasDiff = makeDiffPredicate('deadbeef', { cwd: '/test' });
const hasDiff = makeDiffPredicate('deadbeef', { cwd: '/test' }, undefined, {});
const result = hasDiff({
location: '/test',
});
Expand All @@ -63,7 +65,7 @@ test('ignore changes (globstars)', () => {
'packages/pkg-2/examples/and-another-thing/package.json',
]);

const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, ['**/examples/**', '*.md']);
const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, ['**/examples/**', '*.md'], {});
const result = hasDiff({
location: '/test/packages/pkg-2',
});
Expand All @@ -74,10 +76,62 @@ test('ignore changes (globstars)', () => {
test('ignore changes (match base)', () => {
setup('packages/pkg-3/README.md');

const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, ['*.md']);
const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, ['*.md'], {});
const result = hasDiff({
location: '/test/packages/pkg-3',
});

expect(result).toBe(false);
});

test('exclude subpackages when --independent-subpackages option is enabled and nested package.json is found', () => {
jest.spyOn(globby, 'sync').mockImplementationOnce(() => ['packages/pkg-2/and-another-thing/package.json']);

setup([
'packages/pkg-2/package.json',
'packages/pkg-2/do-a-thing/index.js',
'packages/pkg-2/and-another-thing/package.json',
]);

const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, ['**/examples/**', '*.md'], {
independentSubpackages: true,
});
const result = hasDiff({
location: '/test/packages/pkg-2',
});

expect(result).toBe(true);
expect(childProcesses.execSync).toHaveBeenLastCalledWith(
'git',
['diff', '--name-only', 'v1.0.0', '--', 'packages/pkg-2', ':^packages/pkg-2/packages/pkg-2/and-another-thing'],
{
cwd: '/test',
}
);
});

test('not exclude any subpackages when --independent-subpackages option is enabled but no nested package.json are found', () => {
jest.spyOn(globby, 'sync').mockImplementationOnce(() => []);

setup([
'packages/pkg-2/package.json',
'packages/pkg-2/do-a-thing/index.js',
'packages/pkg-2/and-another-thing/method.js',
]);

const hasDiff = makeDiffPredicate('v1.0.0', { cwd: '/test' }, ['**/examples/**', '*.md'], {
independentSubpackages: true,
});
const result = hasDiff({
location: '/test/packages/pkg-2',
});

expect(result).toBe(true);
expect(childProcesses.execSync).toHaveBeenLastCalledWith(
'git',
['diff', '--name-only', 'v1.0.0', '--', 'packages/pkg-2'],
{
cwd: '/test',
}
);
});
13 changes: 11 additions & 2 deletions packages/core/src/utils/collect-updates/collect-updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ export function collectUpdates(
commandOptions: UpdateCollectorOptions,
dryRun = false
) {
const { forcePublish, conventionalCommits, conventionalGraduate, excludeDependents, isIndependent } = commandOptions;
const {
forcePublish,
conventionalCommits,
conventionalGraduate,
excludeDependents,
independentSubpackages,
isIndependent,
} = commandOptions;

// If --conventional-commits and --conventional-graduate are both set, ignore --force-publish
const useConventionalGraduate = conventionalCommits && conventionalGraduate;
Expand Down Expand Up @@ -96,7 +103,9 @@ export function collectUpdates(

log.info('', `Looking for changed packages since ${committish}`);

const hasDiff = makeDiffPredicate(committish as string, execOpts, commandOptions.ignoreChanges as string[]);
const hasDiff = makeDiffPredicate(committish as string, execOpts, commandOptions.ignoreChanges as string[], {
independentSubpackages,
});
const needsBump =
!commandOptions.bump || commandOptions.bump.startsWith('pre')
? () => false
Expand Down
39 changes: 32 additions & 7 deletions packages/core/src/utils/collect-updates/lib/make-diff-predicate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import globby, { GlobbyOptions } from 'globby';
import log from 'npmlog';
import minimatch from 'minimatch';
import path from 'path';
Expand All @@ -11,7 +12,12 @@ import { ExecOpts } from '../../../models';
* @param {import("@lerna/child-process").ExecOpts} execOpts
* @param {string[]} ignorePatterns
*/
export function makeDiffPredicate(committish: string, execOpts: ExecOpts, ignorePatterns: string[] = []) {
export function makeDiffPredicate(
committish: string,
execOpts: ExecOpts,
ignorePatterns: string[] = [],
diffOpts: { independentSubpackages?: boolean }
) {
const ignoreFilters = new Set(
ignorePatterns.map((p) =>
minimatch.filter(`!${p}`, {
Expand All @@ -27,7 +33,7 @@ export function makeDiffPredicate(committish: string, execOpts: ExecOpts, ignore
}

return function hasDiffSinceThatIsntIgnored(/** @type {import("@lerna/package-graph").PackageGraphNode} */ node) {
const diff = diffSinceIn(committish, node.location, execOpts);
const diff = diffSinceIn(committish, node.location, execOpts, diffOpts);

if (diff === '') {
log.silly('', 'no diff found in %s', node.name);
Expand Down Expand Up @@ -56,17 +62,36 @@ export function makeDiffPredicate(committish: string, execOpts: ExecOpts, ignore
/**
* @param {string} committish
* @param {string} location
* @param {import("@lerna/child-process").ExecOpts} opts
* @param {import("@lerna/child-process").ExecOpts} execOpts
*/
function diffSinceIn(committish, location, opts) {
function diffSinceIn(
committish: string,
location: string,
execOpts: ExecOpts,
diffOpts: { independentSubpackages?: boolean }
) {
const args = ['diff', '--name-only', committish];
const formattedLocation = slash(path.relative(opts.cwd, location));
const formattedLocation = slash(path.relative(execOpts.cwd, location));

if (formattedLocation) {
// avoid same-directory path.relative() === ""
args.push('--', formattedLocation);
let independentSubpackages: string[] = [];

// optionally exclude sub-packages
if (diffOpts?.independentSubpackages) {
independentSubpackages = globby
.sync('**/*/package.json', {
cwd: formattedLocation,
nodir: true,
ignore: '**/node_modules/**',
} as GlobbyOptions)
.map((file) => `:^${formattedLocation}/${path.dirname(file)}`);
}

// avoid same-directory path.relative() === ""
args.push('--', formattedLocation, ...independentSubpackages);
}

log.silly('checking diff', formattedLocation);
return execSync('git', args, opts);
return execSync('git', args, execOpts);
}
9 changes: 9 additions & 0 deletions packages/version/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Running `lerna version --conventional-commits` without the above flags will rele
- [`--changelog-version-message <msg>`](#--changelog-version-message-msg) (new)
- [`--create-release <type>`](#--create-release-type)
- [`--exact`](#--exact)
- [`--independent-subpackages`](#--independent-subpackages)
- [`--force-publish`](#--force-publish)
- [`--git-tag-command <cmd>`](#--git-tag-command-cmd) (new)
- [`--dry-run`](#--dry-run) (new)
Expand Down Expand Up @@ -417,6 +418,14 @@ When run with this flag, `lerna version` will specify updated dependencies in up

For more information, see the package.json [dependencies](https://docs.npmjs.com/files/package.json#dependencies) documentation.

### `--independent-subpackages`

```sh
lerna version --independent-subpackages
```

If `package B`, being a child of `package A`, has changes they will normally both get bumped although `package A` itself is eventually unchanged. If this flag is enabled and only `package B` was actually changed, `package A` will not get bumped if it does not have any changes on its own.

### `--force-publish`

```sh
Expand Down

0 comments on commit dfd0a78

Please sign in to comment.