Skip to content

Instantly share code, notes, and snippets.

@devinrhode2
Last active November 1, 2022 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devinrhode2/43d0dfe985365434e2cba78fd6823799 to your computer and use it in GitHub Desktop.
Save devinrhode2/43d0dfe985365434e2cba78fd6823799 to your computer and use it in GitHub Desktop.
Example apps as automated tests, in a separate git repo.

Let's imagine you produce an sdk, an npm package named @crazy/foo,

Scenario:

You like monorepos, but you have example apps in separate repos, and those example apps can't be brought into the monorepo for some reason.

You want those example apps to also function as automated tests, and, you want them to be updated in lockstep with @crazy/foo sdk changes.

Devin finds monorepos are always better, and believes it's best to work from first principles to solve any problem you have, in a way that does not involve multiple git repos.

For example, if you want to make it easy for sdk consumers to easily kick start a new app using one of your examples, Rich Harris's degit tool allows you to do exactly that:

To clone a specific subdirectory instead of the entire repo, just add it to the argument:

degit user/repo/subdirectory

If a monorepo approach is simply not possible any other way, here is how we can mostly recover the monorepo DX:

Easiest approach:

Just add something to your PR template:

<!-- If you've made any breaking changes to example apps(?),
include a link to PRs here which address those breaking changes (???) -->

Quite unsatisfying. No guarantees.

Automated tests

Maybe we can enforce devs update the example apps, somehow?

We could have a ci/cd process:

  1. Run build in monorepo, creating new @crazy/foo package
  2. git clone react-chat-demo#master
  3. npm link ../monorepo/build/@crazy/foo
  4. npm test

Problem is, if you made changes which break the example app, the tests will fail. We can't point the ci/cd process to react-chat-demo's master branch. We need to point the ci/cd process to your updated react-chat-demo branch, sdk-v2-changes.

Further, we want the two PRs to merge at roughly the same time.

Even better: Alleviate the need to create a second github PR in the example app, somehow make the example apps changes appear in the monorepo PR.

An obvious approach is to just update the branch for react-chat-demo directly in the ci/cd pipeline test. This is a little bit messy, because we need some additional step to correct the branch name back to master once everything is said and done.

Maybe you DO want to first break the example app, then merge the PR that fixes it. Assumption here is you don't want this, because fixing the example app may inspire some api changes, and you only want to merge code to master once it's in really good shape.

Git submodules

Submodules can help solve this branch pointer problem.

The job of submodules, really, is to point to specific versions of other git repos, and include their source in the monorepo repo as a normal folder of code.

git submodules tldr:

  • Similar to any module system.
  • There's a name to the module.
  • There's a version of the module.
  • Git adds a .gitmodules file to your monorepo (Similar to package.json)
    • This contains a git clone url to the other git repo.
  • The "version" of this submodule is determined simply by a sha, branch names can't help much here.

Let's imagine you've already added the example app as a proper git submodule:

git submodule add -- git@github.com:crazy/react-chat-demo.git ./examples/react-chat-demo

The real complexity is in managing code over time: What does a PR in the monorepo do?

If a monorepo PR effects the example app, the monorepo PR needs to contain an update to the git sha (i.e. version) of the submodule.

A developer would need to create a new branch in the example app repo:

cd ./examples/react-chat-demo;
git checkout -b sdk-v2-changes;
git add -p; git commit;

However, this can be automated to some extent.

The ci/cd pipeline now simply runs cd ./examples/react-chat-demo; instead of git clone. As long as you've updated the sha of the gitsubmodule in the parent monorepo (i.e. ran git submodule update - akin to yarn update react), the ci/cd pipeine will see the correct example app (sdk-v2-changes).

Of course, managing two PRs any time you effect the example app is going to get real annoying real fast.

A special bot could actually force update the example app's master branch whenever the submodule sha in the monorepo changes.

That is, you don't necessarily need to create a second PR in the example app.

When you update the sha of a submodule in github, github will actually show the submodule diff inline, in the monorepo PR.

So long as reviewers are actually checking that submodule diff, we don't need a second PR in the example app repo.

Once a monorepo PR is merged into master, this bot could update the submodule master branch:

cd react-chat-demo;
git checkout master;
git pull;
# Merge conflicts admittedly are an unhandled edge case:
git merge -X ours <new-submodule-sha-branch>;
git push --force;

Conclusion

This makes it easy to make breaking changes to the example app.

It's easy to keep example apps updated since they are just another folder of code inside of the monorepo.

There is some git overhead due to making the example app a separate repo, but the costs/DX around git submodules can be improved and automated to a large extent.

This also turns our example apps into full e2e test suites for the SDK.

So long as the tests in the example app sufficiently exercise the SDK, SDK updates will not be allowed to merge unless they also update the example app to have passing tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment