packages feed

seihou-cli-0.4.0.0: help/migrations.md

MIGRATIONS

A migration is an author-declared sequence of file-system operations that
moves a project's working tree from one module version to another. When a
module is upgraded — say, from haskell-base 1.0.0 to 2.0.0 — the project
that has it applied may need to rename directories, drop files that v2
doesn't ship, or run a one-off command. Migrations let module authors
ship that knowledge inside `module.dhall` so consumers don't have to read
a CHANGELOG and reconcile by hand.

WHEN TO AUTHOR A MIGRATION

  Add a migration when a new version of your module changes the *layout*
  of the files it generates: a directory rename, a file removal, a path
  pattern shift. You do not need a migration for content-only changes;
  re-running `seihou run <module>` already updates file content. Use
  migrations specifically for things that `seihou run` cannot infer on
  its own.

THE MIGRATION RECORD

  Each migration is one record in the `migrations` field of `module.dhall`:

    , migrations =
        [ S.Migration::{ from = "1.0.0"
                       , to = "2.0.0"
                       , ops =
                           [ S.MigrationOp.MoveDir
                               { src = "app", dest = "src" }
                           , S.MigrationOp.DeleteFile
                               { path = "Setup.hs" }
                           ]
                       }
        ]

  - `from` and `to` are dotted versions parsed by `parseVersion`.
  - `ops` is the list of operations applied in order.

OPERATIONS

  Authors compose migrations from five typed operations:

  MoveFile    Rename a single tracked file. The engine rewrites the
              manifest's files-map key from src to dest so subsequent
              `seihou status` and `seihou diff` keep working.

                S.MigrationOp.MoveFile { src = "lib/Old.hs", dest = "src/New.hs" }

  MoveDir     Rename a directory. Every manifest entry whose path lies
              under src/ has its key rewritten with the dest/ prefix.

                S.MigrationOp.MoveDir { src = "app", dest = "src" }

  DeleteFile  Remove a tracked file. Drops the manifest entry.

                S.MigrationOp.DeleteFile { path = "Setup.hs" }

  DeleteDir   Recursively remove a directory and drop every manifest
              entry under that prefix.

                S.MigrationOp.DeleteDir { path = "obsolete" }

  RunCommand  Run a shell command. The escape hatch for changes the
              other ops can't express. The manifest is *not*
              automatically updated; if your command moves files, you
              are responsible for following it with explicit
              MoveFile/DeleteFile ops or living with manifest drift.

                S.MigrationOp.RunCommand
                  { run = "cabal run my-tool -- migrate", workDir = None Text }

WINDOW SEMANTICS

  When a user runs `seihou migrate`, the planner walks every declared
  migration whose [from, to] range falls inside [installed, target] in
  ascending `from` order, advancing a cursor as it goes. Migrations
  whose `from` is already past the cursor (overlap or stale) are
  skipped silently; migrations whose `to` exceeds the target are
  skipped (a future invocation with a higher target will pick them
  up).

  The cursor never has to land on a declared `from`. Gaps are
  permitted:

    installed: 0.2     target: 0.6
    declared:  0.2 → 0.3,   0.5 → 0.6

  Both edges run in order. The 0.3 → 0.5 gap has no declared
  migration and the planner does not stop — it skips the gap and
  keeps walking.

MANIFEST ALWAYS LANDS AT TARGET

  After a successful (non-dry-run) migration, the manifest's recorded
  version for the module advances to the supplied target — regardless
  of whether any of the declared migrations bridged every gap. A plan
  with zero in-window migrations is a "pure version bump" that
  advances the manifest's `moduleVersion` field without running any
  ops. The terms "blocked migration", "benign upgrade", and
  "bump-through" are gone; every invocation either has nothing to do
  (`installed == target`) or lands the manifest at the target.

DUPLICATES AND OVERLAPS

  If two migrations share the same `from`, that is an ambiguity and
  the planner refuses (`MigrationDuplicateEdge`). Authors who want a
  "leapfrog" migration alongside the smaller steps should declare the
  longer span and let the smaller overlapping edges sit unused — the
  cursor will advance past them after picking the leapfrog.

CONFLICT SEMANTICS

  Mirroring `seihou remove`, the engine classifies each
  file-targeted op into one of three states before touching disk:

    MFSafe     Disk hash matches the manifest's recorded hash. Free
               to move or delete.
    MFConflict Disk hash differs. The user has edited the file since
               it was generated. The engine refuses to overwrite
               unless `--force` is passed.
    MFGone     File is absent on disk. The op is a no-op.

  The conflict check happens up front. With one or more `MFConflict`
  paths and no `--force`, no disk mutation occurs. With `--force`, the
  migration proceeds and the user-edited bytes ride along (a
  MoveFile preserves content; a DeleteFile drops it).

DRY RUN

  `seihou migrate <module> --dry-run` prints the plan and exits. The
  output is identical to a real run, minus the actual disk
  modifications. Use this before any non-trivial migration to see
  what would be touched.

  For a machine-readable form, pass `--json` (works alongside
  `--dry-run`).

UPGRADE INTEGRATION

  `seihou upgrade` updates the central installed copy under
  ~/.config/seihou/installed/<name>/ but does *not* rewrite project
  trees by default. After an upgrade that brings new migrations,
  `seihou upgrade` prints a one-line advisory:

    note: haskell-base has 1 migration(s) pending (1.0.0 → 2.0.0);
          run 'seihou migrate haskell-base'

  Pass `--with-migrations` to skip the advisory and run migrations
  for each upgraded module against the current project in one shot.

STATUS INTEGRATION

  `seihou status` shows a `Pending migration: <from> -> <to> (<N>
  step(s)). Run: seihou migrate <name>` sub-line under any applied
  module whose installed copy has advanced past the manifest's
  recorded version. `<N>` is the count of in-window declared
  migrations that will run; it may be zero (the version range has no
  applicable migrations and the run will only advance the manifest).
  The line is informational — use `seihou migrate <module>` to apply.

MANIFEST GUARANTEE

  After a successful (non-dry-run) migration, the manifest's files map
  reflects the new paths exactly: a MoveFile rewrites one key, a
  MoveDir rewrites every contained key, deletes drop their entries,
  and the manifest's `genAt` is bumped. The named applied module's
  `moduleVersion` is updated to the supplied target (which may differ
  from the highest `to` in the applied migrations — see "Manifest
  always lands at target" above). `seihou diff` after a clean
  migration is empty; `seihou status` shows no pending migrations.

SEE ALSO

  seihou migrate --help
  seihou upgrade --help
  docs/user/migrations.md