-
Notifications
You must be signed in to change notification settings - Fork 3
Initial work #1
Changes from all commits
e214b8f
e1f8d93
04b8b4d
b386cb6
48525dc
1748537
fd07c3e
5091b72
9eb9418
3b18eba
d9311b0
6a1a379
5a537ce
5e6c4ba
b68f252
7e5992a
6df2da1
1a15eea
e7eb523
348c8c4
fc7166a
1446453
966080f
b64412b
c9d4724
526d191
11111b7
6dc649b
de6527e
dde678a
1165993
64217df
e18e6d0
1047d1a
c836ed7
494e1c9
3d29da1
991fadf
6649ea4
7aa59d8
54c7cbb
f5f3ddf
43d0066
6715d0d
c665a1d
e819342
e7b6867
f89b530
d19b486
405d259
4ab9c4d
77ca00e
f6787a7
94cdc7a
4fc8244
188268a
1a6d6c6
3e4415d
a0ce795
6996b6f
edc7530
724d02b
c93c732
813f2aa
a3d1712
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[*] | ||
indent_style = space | ||
indent_size = 2 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
end_of_line = lf | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
* text=auto | ||
test/test-repo/** text eol=lf |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
docs | ||
|
||
.eslintrc | ||
|
||
# Logs | ||
logs | ||
*.log | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
build | ||
dist | ||
|
||
# Dependency directory | ||
node_modules | ||
|
||
# Tests | ||
test/test-repo-for* | ||
test/sharness/tmp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
language: node_js | ||
cache: npm | ||
stages: | ||
- check | ||
- test | ||
- cov | ||
|
||
node_js: | ||
- '10' | ||
|
||
os: | ||
- linux | ||
- osx | ||
- windows | ||
|
||
script: npx nyc -s npm run test:node -- --bail | ||
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov | ||
|
||
jobs: | ||
include: | ||
- stage: check | ||
script: | ||
- npx aegir commitlint --travis | ||
- npx aegir dep-check | ||
- npm run lint | ||
|
||
- stage: test | ||
name: chrome | ||
addons: | ||
chrome: stable | ||
script: npx aegir test -t browser | ||
|
||
- stage: test | ||
name: firefox | ||
addons: | ||
firefox: latest | ||
script: npx aegir test -t browser -- --browsers FirefoxHeadless | ||
|
||
notifications: | ||
email: false | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,277 @@ | ||
# js-ipfs-migrator | ||
# Migration tool for JS IPFS Repo | ||
|
||
[](https://travis-ci.com/ipfs/js-ipfs-repo-migrations) | ||
[](https://codecov.io/gh/ipfs/js-ipfs-repo-migrations) | ||
[](https://david-dm.org/ipfs/js-ipfs-repo-migrations) | ||
[](http://ipn.io) | ||
[](http://ipfs.io/) | ||
[](http://webchat.freenode.net/?channels=%23ipfs) | ||
[](https://github.com/RichardLitt/standard-readme) | ||
[](https://github.com/feross/standard) | ||
 | ||
 | ||
|
||
> Migration framework for versioning of JS IPFS Repo | ||
|
||
This package is inspired by the [go-ipfs repo migration tool](https://github.com/ipfs/fs-repo-migrations/) | ||
|
||
## Lead Maintainer | ||
|
||
[Adam Uhlíř](https://github.com/auhau/) | ||
|
||
## Table of Contents | ||
|
||
- [Background](#background) | ||
- [Install](#install) | ||
- [npm](#npm) | ||
- [Use in Node.js](#use-in-nodejs) | ||
- [Use in a browser with browserify, webpack or any other bundler](#use-in-a-browser-with-browserify-webpack-or-any-other-bundler) | ||
- [Use in a browser Using a script tag](#use-in-a-browser-using-a-script-tag) | ||
- [Usage](#usage) | ||
- [Writing migration](#writing-migration) | ||
- [Migrations matrix](#migrations-matrix) | ||
- [API](#api) | ||
- [CLI](#cli) | ||
- [Versioning](#versioning) | ||
- [Contribute](#contribute) | ||
- [License](#license) | ||
|
||
## Background | ||
|
||
|
||
As js-ipfs evolves and new technologies, algorithms and data structures are incorporated it is necessary to | ||
enable users to transition between versions. Different versions of js-ipfs may expect a different IPFS repo structure or content (see: [IPFS repo spec](https://github.com/ipfs/specs/tree/master/repo), [JS implementation](https://github.com/ipfs/js-ipfs-repo) ). | ||
So the IPFS repo is versioned, and this package provides a framework to create migrations to transition | ||
from one version of IPFS repo to the next/previous version. | ||
|
||
This framework: | ||
* Handles locking/unlocking of repository | ||
* Defines migrations API | ||
* Executes and reports migrations in both directions: forward and backward | ||
* Simplifies creation of new migrations | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Works on the browser too! | ||
|
||
## Install | ||
|
||
### npm | ||
|
||
```sh | ||
> npm install ipfs-repo-migrations | ||
``` | ||
|
||
### Use in Node.js | ||
|
||
```js | ||
const migrations = require('ipfs-repo-migrations') | ||
``` | ||
|
||
### Use in a browser with browserify, webpack or any other bundler | ||
|
||
```js | ||
const migrations = require('ipfs-repo-migrations') | ||
``` | ||
|
||
## Usage | ||
|
||
Example: | ||
|
||
```js | ||
const migrations = require('ipfs-repo-migrations') | ||
const getVersion = require('ipfs-repo-migrations/repo/version') | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const repoPath = 'some/repo/path' | ||
const repoVersion = await getVersion(repoPath) | ||
|
||
if(repoVersion < migrations.getLatestMigrationVersion()){ | ||
// Old repo! Lets migrate to latest version! | ||
await migrations.migrate(repoPath) | ||
} | ||
``` | ||
|
||
To migrate your repository using the CLI, see the [how to run migrations](./run.md) tutorial. | ||
|
||
## API | ||
|
||
### `.migrate(path, {toVersion, ignoreLock, repoOptions, onProgress, isDryRun}) -> Promise<void>` | ||
|
||
Executes a forward migration to a specific version, or to the latest version if a specific version is not specified. | ||
|
||
**Arguments:** | ||
|
||
* `path` (string, mandatory) - path to the repo to be migrated | ||
* `options` (object, optional) - options for the migration | ||
* `options.toVersion` (int, optional) - version to which the repo should be migrated. Defaults to the latest migration version. | ||
* `options.ignoreLock` (bool, optional) - if true will not lock the repo when applying migrations. Use with caution. | ||
* `options.repoOptions` (object, optional) - options that are passed to migrations, that use them to construct the datastore. (options are the same as for IPFSRepo). | ||
* `options.onProgress` (function, optional) - callback that is called after finishing execution of each migration to report progress. | ||
* `options.isDryRun` (bool, optional) - flag that indicates if it is a dry run that should give the same output as running a migration but without making any actual changes. | ||
|
||
#### `onProgress(migration, counter, totalMigrations)` | ||
|
||
Signature of the progress callback. | ||
|
||
**Arguments:** | ||
* `migration` (object) - object of migration that just successfully finished running. See [Architecture of migrations](#architecture-of-migrations) for details. | ||
* `counter` (int) - index of current migration. | ||
* `totalMigrations` (int) - total count of migrations that will be run. | ||
|
||
### `.revert(path, toVersion, {ignoreLock, options, onProgress, isDryRun}) -> Promise<void>` | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Executes backward migration to a specific version. | ||
|
||
**Arguments:** | ||
|
||
* `path` (string, mandatory) - path to the repo to be reverted | ||
* `toVersion` (int, mandatory) - version to which the repo should be reverted to. | ||
* `options` (object, optional) - options for the reversion | ||
* `options.ignoreLock` (bool, optional) - if true will not lock the repo when applying migrations. Use with caution. | ||
* `options.options` (object, optional) - options that are passed to migrations, that use them to construct the datastore. (options are the same as for IPFSRepo). | ||
* `options.onProgress` (function, optional) - callback that is called after finishing execution of each migration to report progress. | ||
* `options.isDryRun` (bool, optional) - flag that indicates if it is a dry run that should give the same output as running a migration but without making any actual changes. | ||
|
||
### `getLatestMigrationVersion() -> int` | ||
|
||
Return the version of the latest migration. | ||
|
||
## CLI | ||
|
||
The CLI is a NodeJS binary named `jsipfs-repo-migrations`. | ||
It has several commands: | ||
|
||
* `migrate` - performs forward/backward migration to specific or latest version. | ||
* `status` - check repo for migrations that should be run. | ||
* `add` - bootstraps new migration. | ||
|
||
For further details see the `--help` pages. | ||
|
||
## Creating a new migration | ||
|
||
Migrations are one of those things that can be extremely painful on users. At the end of the day, we want users never to have to think about it. The process should be: | ||
|
||
- SAFE. No data lost. Ever. | ||
- Revertible. Tools must implement forward and backward (if possible) migrations. | ||
- Tests. Migrations have to be well tested. | ||
- To Spec. The tools must conform to the spec. | ||
|
||
If your migration has several parts, it should be fail-proof enough that if one part of migration fails the previous changes | ||
are reverted before propagating the error. If possible then the outcome should be consistent repo so it migration could | ||
be run again. | ||
|
||
### Architecture of a migration | ||
|
||
All migrations are placed in the `/migrations` folder. Each folder there represents one migration that follows the migration | ||
API. | ||
|
||
All migrations are collected in `/migrations/index.js`, which should not be edited manually. It is regenerated on | ||
every run of `jsipfs-migrations add` (manual changes should follow the same style of modifications). | ||
**The order of migrations is important and migrations must be sorted in ascending order**. | ||
|
||
Each migration must follow this API. It must export an object in its `index.js` that has following properties: | ||
|
||
* `version` (int) - Number that represents the version which the repo will migrate to (eg. `migration-8` will move the repo to version 8). | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* `description` (string) - Brief description of what the migrations does. | ||
* `migrate` (function) - Function that performs the migration (see signature of this function below) | ||
* `revert` (function) - If defined then this function will revert the migration to the previous version. Otherwise it is assumed that it is not possible to revert this migration. | ||
|
||
#### `.migrate(repoPath, isBrowser)` | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
_Do not confuse this function with the `require('ipfs-repo-migrations').migrate()` function that drives the whole migration process!_ | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Arguments: | ||
* `repoPath` (string) - absolute path to the root of the repo | ||
* `options` (object, optional) - object containing `IPFSRepo` options, that should be used to construct a datastore instance. | ||
* `isBrowser` (bool) - indicates if the migration is run in a browser environment (as opposed to NodeJS) | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### `.revert(repoPath, isBrowser)` | ||
|
||
_Do not confuse this function with the `require('ipfs-repo-migrations').revert()` function that drives the whole backward migration process!_ | ||
|
||
Arguments: | ||
* `repoPath` (string) - path to the root of the repo | ||
* `options` (object, optional) - object containing `IPFSRepo` options, that should be used to construct the datastore instance. | ||
* `isBrowser` (bool) - indicates if the migration is run in a browser environment (as opposed to NodeJS) | ||
|
||
### Browser vs. NodeJS environments | ||
|
||
The migration might need to distinguish in which environment it runs (browser vs. NodeJS). For this reason there is an argument | ||
`isBrowser` passed to migrations functions. But with simple migrations it should not be necessary to distinguish between | ||
these environments as the datastore implementation will handle the main differences. | ||
|
||
There are currently two main datastore implementations: | ||
1. [`datastore-fs`](https://github.com/ipfs/js-datastore-fs) that is backed by file system and is used mainly in the NodeJS environment | ||
2. [`datastore-level`](https://github.com/ipfs/js-datastore-level) that is backed by LevelDB and is used mainly in the browser environment | ||
|
||
Both implementations share the same API and hence are interchangeable. | ||
|
||
When the migration is run in a browser environment, `datastore-fs` is automatically replaced with `datastore-level` even | ||
when it is directly imported (`require('datastore-fs')` will return `datastore-level` in a browser). | ||
So with simple migrations you shouldn't worry about the difference between `datastore-fs` and `datastore-level` | ||
and by default use the `datastore-fs` package (as the replace mechanism does not work vice versa). | ||
|
||
### Guidelines | ||
|
||
The recommended way to write a new migration is to first bootstrap a dummy migration using the CLI: | ||
|
||
```sh | ||
> npm run new-migration | ||
``` | ||
|
||
A new folder is created with the bootstrapped migration. You can then simply fill in the required fields and | ||
write the rest of the migration! | ||
|
||
### Integration with js-ipfs | ||
|
||
When a new migration is created, the repo version in [`js-ipfs-repo`](https://github.com/ipfs/js-ipfs-repo) should be updated with the new version, | ||
together with updated version of this package. Then the updated version should be propagated to `js-ipfs`. | ||
AuHau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Tests | ||
|
||
If a migration affects any of the following functionality, it must provide tests for the following functions | ||
to work under the version of the repo that it migrates to: | ||
|
||
* `/src/repo/version.js`:`getVersion()` - retrieving repository's version | ||
* `/src/repo/lock.js`:`lock()` - locking repository that uses file system | ||
* `/src/repo/lock-memory.js`:`lock()` - locking repository that uses memory | ||
|
||
Every migration must have test coverage. Tests for migrations should be placed in the `/test/migrations/` folder. Most probably | ||
you will have to plug the tests into `browser.js`/`node.js` if they require specific bootstrapping on each platform. | ||
|
||
### Empty migrations | ||
|
||
For interop with go-ipfs it might be necessary just to bump a version of a repo without any actual | ||
modification as there might not be any changes needed in the JS implementation. For that purpose you can create an "empty migration". | ||
|
||
The easiest way to do so is with the CLI: | ||
|
||
```sh | ||
> npm run new-migration -- --empty | ||
``` | ||
|
||
This will create an empty migration with the next version. | ||
|
||
### Migrations matrix | ||
|
||
| IPFS repo version | JS IPFS version | | ||
| -----------------: |:----------------:| | ||
| 7 | v0.0.0 - latest | | ||
|
||
## Developer | ||
|
||
### Module versioning notes | ||
|
||
In order to have good overview of what version of package contains what kind of migrations, this package follows this versioning schema: `0.<versionOfLastMigration>.<patches>`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tying the npm version to the repo version like this means we lose the ability to communicate breaking/feature change information for this module to dependants. For example, how would we communicate a change like the async/await refactor in this module? There's no repo version increase and we can't release a breaking change like that in a patch release. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, you are right about this, but I don't believe there will be much of feature development/breaking changes in the future of this package. I believe that 80% of the changes will be adding new migrations, then 15% will be fixing bugs and 5% others... In your example, we could communicate the breaking change using the major version. So for example from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We could...IMHO that would be a really strange versioning strategy though!
I kind of do too, but I'm not convinced it's worth it! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
True, but following What about having two parts in the version? E.g. What I am not sure is if this schema would not break some tooling? (aegir?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there another reason other than convenience that would warrant this versioning strategy? If you don't already know the module the versioning strategy isn't going to convey any additional info. Secondly, it's really easy for new developers to miss this important piece of info since it's a small section at the bottom of the README. Thirdly, if you do know the module and are upgrading to get the latest migrations you'll probably just need the latest version of this module anyway right? It's quite unlikely you'll ever need an older version of the module since the newer versions contain all the past migrations as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would not call it convenience, but being careful. Having a different version of this package installed may lead to irreversible actions that can happen without your knowledge (eq. when you have Of course, having this schema won't prevent all mix ups, but it can improve the situation. From version update In this light, I think having the I am not sure if my thoughts about this are not getting too tangled and strayed from reality, but I still feel like there are more pros over cons with this schema. Actually are there any cons? Except for the possible tooling side? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you feel strongly about this I'll not stand in your way any further. I'm undecided if I agree that a pre-release suffix is a better strategy than using the regular major/minor/patch versions. My issue with this it is that according to semver:
So this would convey to devs that it is not a stable version that should not be used without accepting some instability consequences. I also do not know if AEgir will support you on this 😛 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm. Agree that this is a bit unfortunate. I checked finally SemVer spec and it directly supports "build metadata", which I feel could be exactly used for this. It stands aside in determining version precedence, which is a good thing. We could have then schema |
||
|
||
## Contribute | ||
|
||
There are some ways you can make this module better: | ||
|
||
- Consult our [open issues](https://github.com/ipfs/js-ipfs-repo/issues) and take on one of them | ||
- Help our tests reach 100% coverage! | ||
|
||
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). | ||
|
||
[](https://github.com/ipfs/community/blob/master/contributing.md) | ||
|
||
## License | ||
|
||
[MIT](LICENSE) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
'use strict' | ||
|
||
// Do not modify this file manually as it will be overridden when running 'add' CLI command. | ||
// Modify migration-templates.js file | ||
|
||
const emptyMigration = { | ||
description: 'Empty migration.', | ||
migrate: () => {}, | ||
revert: () => {}, | ||
empty: true, | ||
} | ||
|
||
module.exports = [ | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mind adding this file to .gitignore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I can, but I think it is a good thing to have around as it unities the code style. I know that for that there is
aegir lint
but this integrates directly with IDEs. You don't use it in other projects? I feel like I took it from some other JS package...For example here: https://github.com/ipfs/js-ipfs/blob/master/.editorconfig