Skip to content
This repository has been archived by the owner on Dec 5, 2018. It is now read-only.

implement tool for publishing packages from a mono-repo #1

Closed
wants to merge 5 commits into from

Conversation

kevinbarabash
Copy link
Member

@kevinbarabash kevinbarabash commented Jun 17, 2018

The reason for creating this tool is that lerna does a couple of things things which we'd like to avoid with publishing packages from the wonder-blocks mono-repo:

  • making changes to a dependency will cause lerna to automatically bump the version of packages that depend on it… this feels fundamentally wrong since the dependee may not be ready to use the updated version of the dependency. Also, if the dependee is using version ^1.0.0 and the dependency is bumped to 1.1.0, then there's no reason the update the dependee's version.
  • right now lerna is responsible for bumping versions (as well as publishing) which means that you have to commit changes without bumping versions. In repos that publish a single package this is fine b/c other packages can’t rely on in between states. This is different in mono-repos b/c you may change the version of a dependency in the same commit that you change a package to rely on that new version. This means that we should be bumping versions in the same commit that we’re making this change. This avoids having commits in the repo that you can never checkout and run b/c of an inconsistent state. The publish (and tagging) can and should be done in a separate commit.

TODO:

  • set up travis-ci
  • push the created tags to GitHub after publishing

Test Plan:

  • yarn test
  • npm link
  • checkout https://github.com/kevinbarabash/workspaces-test
  • bump versions of foo and bar packages in workspaces-test and commit changes
  • run antwerp --dry-run from workspaces-test folder
  • see that the output contains commands to tag the most recent commit with @kevinbarabash/foo@0.1.10 and @kevinbarabash/bar@0.1.11 and publish @kevinbarabash/foo and @kevinbarabash/bar packages. Notice that it doesn't try to bump or publish @kevinbarabash/foobar which relies on version ^0.1.0 of both of those packages.
  • run antwerp again without the --dry-run flag and verify that it's published those packages to https://npmjs.org/~kevinbarabash and that the most recent commit has indeed been tagged appropriately.

Copy link
Member

@somewhatabstract somewhatabstract left a comment

Choose a reason for hiding this comment

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

I haven't looked at the exact implementation yet. I'll do that when I get to the AirBnB.

"dependencies": {
"@fixture/public-dep": "^0.0.1"
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Love all these test fixtures. Is @fixture a reserved namespace or just something you chose to use just for testing?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's just something I made up fro testing... nothing actually gets published as part of the test tests.

} else {
console.log(`would run: ${command}`);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

No exit code for a successful run?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm going to add a process.exit(0); but it has to come right at the end of the try block.

Copy link
Member

Choose a reason for hiding this comment

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

Or after the catch?

@@ -0,0 +1,22 @@
{
"name": "antwerp",
Copy link
Member

Choose a reason for hiding this comment

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

I like it, but why antwerp?

Copy link
Member Author

Choose a reason for hiding this comment

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

Naming things is hard. I started out with pubtools but then I thought I'd change it to pubtool since this is really only one tool, but that's already taken. I started thinking about pubs I've been in in videos games and there was one in Hero's Quest I (aka Quest for Glory I) where you have to say a secret password to the goon guarding the entrance to the thieves' guild. One of the passwords was antwerp. I'm open to better names as this name is not very descriptive and only tangentially related to the task that the tool performs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, antwerp is the name of this creature in the game:
6c45017fb313c6dd0445afbecab640a7 gif

In my journey through Benelux in the spring I visited Antwerp the city which has this really old wooden escalator which leads to a tunnel that you can walk, bike, motorbike through. Unsurprisingly it smells really woody. If you're ever in Antwerp it's a must see... along with the MAS.

package.json Outdated
{
"name": "antwerp",
"version": "0.0.1",
"description": "",
Copy link
Member

Choose a reason for hiding this comment

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

Definitely needs a description! 😁

"npm publish --access=public packages/public-dep",
"git tag @fixture/public-pkg@0.0.1",
"npm publish --access=public packages/public-pkg",
]);
Copy link
Member

Choose a reason for hiding this comment

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

I like the ease with which we can test this without necessarily having to run it fully although there's no substitute for actually running and checking those outputted commands really work.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's tough b/c there isn't a good way to mock out npm. At least with git I can run commands locally and verify things about the state of the local repo.


// We assume that if a there's a tag for the current version of a package that
// it's been published already. In the future we should check if that version
// exists on npm and attempt to republish the package if it isn't.
Copy link
Member

Choose a reason for hiding this comment

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

Capture that in an issue.

Copy link
Member Author

Choose a reason for hiding this comment

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

Captured in #3.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can update the comment to link back to the GitHub issue.

src/utils.js Outdated
// Check that depedency versions are satisified
// TODO: fallback to check if any tags exist that would satisfy the dep
if (!semver.satisfies(pkgVerMap[depName], deps[depName])) {
throw new Error(`${depName}@${pkgVerMap[depName]} doesn't satify ${depName}@${deps[depName]}`)
Copy link
Member

Choose a reason for hiding this comment

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

NIT: "satify" should be "satisfy"

src/utils.js Outdated
throw new Error(`${depName}@${pkgVerMap[depName]} doesn't satify ${depName}@${deps[depName]}`)
}

// Check to see if any depedencies of public packages are private
Copy link
Member

Choose a reason for hiding this comment

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

NIT: "depedencies" should be "dependencies"

child_process.execSync("git push --tags");
} else {
console.log(`would run: git push --tags`);
}
Copy link
Member

Choose a reason for hiding this comment

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

Feel like the text git push --tags should be in a constant, and then there should be an maybeRunCommand command that takes that string like this:

function (command, description) {
    console.log(description);
    const isDryRun = argv["dry-run"];
    if (isDryRun) {
        console.log(`would run: ${command}`);
    } else {
        child_process.execSync(command);
    }
}

That way we can reuse this for any command.

const semver = require("semver");

function getTags() {
return child_process.execSync(`git tag`, {encoding: "utf8"}).split("\n").filter(Boolean);
Copy link
Member

Choose a reason for hiding this comment

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

I find these chains easier to read if they are lined up by the dots.

    return child_process
        .execSync...
        .split...
        .filter...

YMMV

function getWorkspaces() {
const pkgJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
return pkgJson.workspaces.reduce((result, wsGlob) => {
return result.concat(glob.sync(wsGlob));
Copy link
Member

Choose a reason for hiding this comment

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

braces and return are redundant.

// bumps the version automatically
commands.push(`npm publish --access=public ${workspace}`);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

So this getCommands call is actually doing some useful stuff. I wonder if it is clear from the name.

Perhaps generateCommandList or something would be better?

Certainly should add a comment that explains what it's doing.

  • Returns command list for tagging and publishing all public packages

const newVersion = semver.inc(json.version, release);
json.version = newVersion;
fs.writeFileSync(pkgJsonPath, JSON.stringify(json, null, 4));
}
Copy link
Member

Choose a reason for hiding this comment

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

So this is the step that we don't do with antwerp. antwerp assumes you've done all the version changes that you need to and then just ensure that things are tagged with those versions before publish, after asserting that the dependencies are available for those packages.

Is that right?

pkgVerMap[pkgJson.name] = pkgJson.version;
depMap[pkgJson.name] = pkgJson.dependencies || {};
pkgJsonMap[pkgJson.name] = pkgJson;
}
Copy link
Member

Choose a reason for hiding this comment

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

So, this loop is ensuring that even though the dependency may not exist yet (because it's on a version of the package that isn't released), it will when antwerp is finished. Right?

Can we get this loop in its own method to make that a little clearer?

}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Also, this needs to be its own function too, I think. Either the whole loop or its contents.

getTags,
getCommands,
getWorkspaces,
};
Copy link
Member

Choose a reason for hiding this comment

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

Any thoughts on supporting use-cases that want to consume this as an API versus a tool?

Copy link
Member

@somewhatabstract somewhatabstract left a comment

Choose a reason for hiding this comment

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

I think there's a little refactoring to do just to get things clearer.

@kevinbarabash
Copy link
Member Author

We have a new workflow that we're going to try with lerna so I'm going to close this for now.

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.

None yet

2 participants