Skip to content

Conversation

@joakimbeng
Copy link
Member

@joakimbeng joakimbeng commented Nov 28, 2025

Emigrate v2

  • New plugin system
  • New migration context and collection logic
  • Rewrite the "up" command
    • Add a "yes" flag (-y/--yes) that's required in non-interactive mode
    • In interactive mode prompt the user for confirmation before actually running the migrations
  • Rewrite the "remove" command
    • Add a "yes" flag (-y/--yes) that's required in non-interactive mode
    • In interactive mode prompt the user for confirmation before actually remove the migration
  • Rewrite the "list" command
  • Add a "log" command that replaces "up" with the "noExecution" flag
  • Rewrite @emigrate/mysql
  • Rewrite @emigrate/postgres
  • Reporters based on the new plugin system
  • Rewrite @emigrate/reporter-pino
  • Rewrite the default reporters
  • Add support for statement parsing and execution in @emigrate/mysql
  • Add support for statement parsing and execution in @emigrate/postgres
  • Move the "new" command to its own package, e.g. "@emigrate/create"
    • To be able to utilise npm init @emigrate or npx @emigrate/create
    • In case it's run in a repo without Emigrate at all it should help with setting it up
  • Move only the CLI part of the current @emigrate/cli to its own package (keep the name) and rename the leftover package to @emigrate/emigrate

A new plugin system

Instead of having different kinds of plugins there's now a plugin inspired by Astro's integrations that's based on hooks.
Each plugin can decide which hooks to implement and depending on what the plugin decides to do in each hook its role varies.
Thanks to the hooks system it's possible for plugins, or directly in the Emigrate config, to handle graceful shutdowns like discussed in #169

Complete rewrite of migration logic

For each migration command a context is built and when it's run it's going through different phases,
where the first phase is loading config and plugins, which themselves can modify the config (to for instance automatically add additional plugins).

The next phase is for collecting and loading migrations where each plugin can register migrations to run (the collect step) and how to run them (the load step).

Thanks to this it's now possible for plugins to run their own migrations as part of the migration,
for instance if a plugin needs/wants to change the data model for its history table.

With this change Emigrate is no longer relying on/requiring migrations being files either,
a migration can be as simple as an ordinary function.
To ease registering functions as migrations (and to for instance run Emigrate without a CLI) there's now a built-in "inline" plugin that can do that.

This also means that the "directory" option is no longer required and to get the old behaviour the built-in "directory" plugin should be used instead. But as plugins can both read and modify the configuration I might change the config interface so that the "directory" plugin is automatically loaded and used if a "directory" field is set in the config, so it's similar to the old behaviour.

There will be breaking changes

In this change no existing plugins have been changed yet though and the whole feature is a work in progress.

So there's no actual breaking change just yet, as this is built in parallel with the existing functionality, but when it's finished this will be breaking changes everywhere.

Instead of having different kinds of plugins there's now a plugin inspired by Astro's integrations that's based on hooks.
Each plugin can decide which hooks to implement and depending on what the plugin decides to do in each hook its role varies.
For each migration command a context is built and when it's run it's going through different phases,
where the first phase is loading config and plugins, which themselves can modify the config (to for instance automatically add additional plugins).
The next phase is for collecting and loading migrations where each plugin can register migrations to run (the collect step) and how to do it (the load step).
Thanks to this it's now possible for plugins to run their own migrations as part of the migration,
for instance if a plugin needs/wants to change the data model for its history table.
With this Emigrate is no longer relying on migrations being files either,
a migration can be as simple as an ordinary function.
To ease registering functions as migrations (and to for instance run Emigrate without a CLI) there's now a built-in "inline" plugin that can do that.
With this the "directory" option is no longer required and to get the old behaviour the built-in "directory" plugin should be used instead.
As plugins can both read and modify the configuration I might change the config interface so that the "directory" plugin is automatically loaded and used if a "directory" field is set in the config, so it's similar to the old behaviour.
In this change no existing plugins have been changed yet though and the whole feature is a work in progress.
So there's no actual breaking change just yet, as this is built in parallel with the existing functionality.
@changeset-bot
Copy link

changeset-bot bot commented Nov 28, 2025

⚠️ No Changeset found

Latest commit: a75fd95

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@joakimbeng
Copy link
Member Author

@copilot implement a "new-remove" command in a similar fashion to the "new-up" command in this PR and with corresponding tests using the same writing style as in this PR.

Copy link

Copilot AI commented Nov 28, 2025

@joakimbeng I've opened a new pull request, #247, to work on those changes. Once the pull request is ready, I'll request review from you.

@joakimbeng joakimbeng changed the title feat(plugins): do a complete rewrite of the plugin system (WIP) feat(emigrate): the journey to v2 (WIP) Nov 28, 2025
joakimbeng and others added 3 commits December 1, 2025 09:45
* feat(plugins): implement new-remove command

Co-authored-by: joakimbeng <1427383+joakimbeng@users.noreply.github.com>

* fix: add eslint disable for no-await-in-loop and change migration filtering logic

Co-authored-by: joakimbeng <1427383+joakimbeng@users.noreply.github.com>

* test: add test for pre-aborted abortSignal in remove command

Co-authored-by: joakimbeng <1427383+joakimbeng@users.noreply.github.com>

* fix: use for...of instead of forEach in assertStorageRemoved

Co-authored-by: joakimbeng <1427383+joakimbeng@users.noreply.github.com>

* refactor: simplify remove command by removing lock and inline removal

Co-authored-by: joakimbeng <1427383+joakimbeng@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joakimbeng <1427383+joakimbeng@users.noreply.github.com>
@joakimbeng
Copy link
Member Author

@copilot implement a "new-list" command in similar fashion to the "new-up" and "new-remove" commands in this PR. Add corresponding tests written in the same style as the tests for those commands.
The "list" command should use the finish method from the context for each migration, and no database locking or logging should occur

Copy link

Copilot AI commented Dec 1, 2025

@joakimbeng I've opened a new pull request, #248, to work on those changes. Once the pull request is ready, I'll request review from you.

joakimbeng and others added 2 commits December 1, 2025 10:43
Implements the `new-list` command following the v2 plugin architecture
pattern established by `new-up` and `new-remove`.

## Changes

- **`packages/cli/src/commands/new-list.ts`** - List command that
iterates migrations calling `context.finish()` for each. No locking or
logging occurs.

- **`packages/cli/src/commands/new-list.test.ts`** - Tests covering:
  - No migration execution
  - No storage locking/unlocking/logging/waiting
  - Command results for various states
  - Plugin callbacks with correct migration states

- **`packages/cli/src/tests/plugin.ts`** - Added `'pending'` to
`MockedFinished` type for list command compatibility

## Review feedback addressed

- [x] Use `.mockImplementation` instead of `originalDoneHook` variable
- [x] Add test for `assertCommandFailed` when there's a failed migration
- [x] Destructure `cwd` option and pass to `createEmigrateContext`

## Notes

Returns `false` when failed migrations exist in history—`finish()`
propagates the error state by design.

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for
you](https://github.com/aboviq/emigrate/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joakimbeng <1427383+joakimbeng@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants