Skip to content

feat: implement lerna add <pkg>[@version] [--dev]#1069

Merged
evocateur merged 15 commits intolerna:masterfrom
marionebl:feat-implement-lerna-add
Oct 30, 2017
Merged

feat: implement lerna add <pkg>[@version] [--dev]#1069
evocateur merged 15 commits intolerna:masterfrom
marionebl:feat-implement-lerna-add

Conversation

@marionebl
Copy link
Contributor

@marionebl marionebl commented Oct 11, 2017

Description

Given

.
├── lerna.json
├── package.json
└── packages
    ├── package-1
    │   └── package.json # 1.0
    ├── package-2
    │   └── package.json # 2.0
    ├── package-3
    │   └── package.json # 1.0
    └── package-4
        └── package.json # 1.0
# adds @test/package-1@^1.0.0 to package-2,3,4
lerna add @test/package-1 

 # adds @test/package-1@~1 to package-2,3,4
lerna add @test/package-1@~1

# fails with ENOTFOUND
lerna add @test/package-5

# fails with ENOTSATISFIED
lerna add @test/package-1@2 

# noop
lerna add @test/package-1 --scope=@test/package-1 

# adds @test/package-1@^1.0.0 to package-2
lerna add @test/package-1 --scope=@test/package-2 

 # adds @test/package-1@^1.0.0 to package-3,4
lerna add @test/package-1 --ignore=@test/package-2

# @test/package-1 => 2,3,4
# @test/package-2 => 1,3,4  
lerna add @test/package-{1,2} --ignore=@test/package-2 

Motivation and Context

Before

  • To depend on local, unpublished dependency you have to
    • add <dependency> to <depending>/package.json by hand
    • execute lerna bootstrap --scope=<depending>

After

  • lerna add <dependency> --scope=<depending>

How Has This Been Tested?

  • Added unit tests
  • Added integration tests
  • Some smoke tests in private projects

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@gausie
Copy link

gausie commented Oct 25, 2017

Is there anything stopping this being merged?

Copy link
Member

@evocateur evocateur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on, @marionebl!

My apologies for the delay in review, I've been slammed at work.


When run, this command will:

1. Add `package` to each applicable package. Applicable are packages that are not `package` and are in scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to support arbitrary 3rd-party packages, too. If --hoist is enabled, the external dependency should be installed in the root package.json first.

Guarding against installing a local dependency inside itself is good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(it occurs to me that the --hoist behaviour is already dealt with by calling bootstrap at the end of execute, as long as we are also passing along this.options instead of the flags)

Copy link
Contributor Author

@marionebl marionebl Oct 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed installing external dependencies via 4be0d91.

Does make a lot more sense from the user perspective, thanks for the review 👍 .

The change got bigger than I'd like due to the fact I switched to an async control flow.

This is needed due to the network requests we send to determine the correct remote package version ranges to persist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type of thing could be easier to build with async - await. Is there a reason is not used in the codebase so far?


_logError(method, description, err) {
log.error(method, description);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces are nice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed via b34a031

};

export function handler(argv) {
new AddCommand([argv.script, ...argv.args], argv, argv._cwd)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is argv.script here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adressed via e1d5fe5

return false;
}

constructor(input, flags, cwd) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command subclasses don't need constructors, it's all handled in the superclass.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adressed via e6bfdff


const source = FileSystemUtilities.readFileSync(manifestPath);

const file = new JSONFile(manifestPath, source);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really necessary to add JSONFile? You should be using write-pkg, an existing dependency, to write the package.json back to disk, as it already handles alphabetizing the updated (dev-)dependencies.

If you use read-pkg instead of reading the string, you can just merge applicable into the targeted dependencyType and write it back without testing, since the serialization is always alphabetized anyway.

const manifestJson = readPkg.sync(manifestPath, { normalize: false });
const manifestDeps = manifestJson[dependencyType] || {};

// overwrite updated dependencies
manifestJson[dependencyType] = {
  ...manifestDeps,
  ...applicable,
};

writePkg.sync(manifestPath, manifestJson);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used json-file-plus to preserve package.json formatting. As this differs in behaviour from npm and other lerna commands and does not handle key alphabetization, I'll refactor as outlined.

}

packageSatisfied(name, versionRange) {
const pkg = this.packages.find(pkg => pkg.name === name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why not const pkg = this.getPackage(name); here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed via a3a09a2

}

getPackage(name) {
return this.packages.find(pkg => pkg.name === name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the body of packageExists() seems to wish this.packages was aMap()<String(name), Package>...

...or maybe Set()<Package>? Noodling for higher-level refactoring, no worries in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the impression that iterating over all (filtered) packages was more common in the code base, and <Package>[] makes that very easy.


expect.extend(pkgMatchers);

describe("AddCommand", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are brilliant tests, thanks!

@@ -0,0 +1,50 @@
import semver from "semver";

const matchDependency = dependecyType => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be dependencyType. I go cross-eyed typing "dependency" a lot 😭

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed via e4c9f0c

expect(pkgs['package-4']).toDependOn('@test/package-1');
});

test("add to scoped package", async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the current functionality, I'm not sure we need any more integration tests beyond the first. (variations based on args and whatnot are already covered in unit tests)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adressed via efa5cc3

@evocateur evocateur force-pushed the feat-implement-lerna-add branch from 2f04b10 to 9254413 Compare October 30, 2017 22:29
@evocateur
Copy link
Member

Sorry for the force push, I should really stop trying to use the dumb Github UI for resolving merge conflicts (it never, ever works)...

@kachkaev
Copy link

Thank you for for adding this feature @marionebl!

I noticed that placing --dev before package names fails the script and it took a while to investigate. Just curious: is this intentional?

## not in a mono-repo
yarn add something --dev ## works
yarn add --dev something ## works

## monorepo
lerna add something --dev ## works
lerna add --dev something ## ERROR: Missing list of packages to add to your project.

Should not --dev (as well as its alias -D) be used as flags so that they don't swallow an argument?

@kachkaev
Copy link

Created a new issue #1386

@lock
Copy link

lock bot commented Dec 27, 2018

This thread has been automatically locked because there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Dec 27, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants