New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

onetable-cli

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

onetable-cli - npm Package Compare versions

Comparing version 1.2.4 to 1.3.0

204

dist/cli.js

@@ -7,3 +7,3 @@ #!/usr/bin/env node

Migrations:
onetable [all, down, generate, list, outstanding, repeat, reset, status, up, N.N.N]
onetable [all, down, generate, list, named, outstanding, repeat, reset, status, up, N.N.N, NAMED]

@@ -15,8 +15,6 @@ Reads migrate.json:

},
delimiter: ':',
dir: './migrations-directory',
hidden: false,
name: 'table-name',
nulls: false,
typeField: 'type',
onetable: {
name: 'table-name',
},
aws: {accessKeyId, secretAccessKey, region},

@@ -37,9 +35,12 @@ arn: 'lambda-arn'

import SenseLogs from 'senselogs';
// import SenseLogs from '../../senselogs/dist/mjs/index.js'
const MigrationTemplate = `
import Schema from 'your-onetable-schema'
export default {
version: 'VERSION',
schema: Schema,
description: 'Purpose of this migration',
async up(db, migrate, params) {
if (!params.dry) {
// db.log.info('Running upgrade')
// await db.create('Model', {})

@@ -54,8 +55,2 @@ }

}`;
const Types = {
String: 'string',
Number: 'number',
Boolean: 'boolean',
String: 'string',
};
const Usage = `

@@ -71,8 +66,11 @@ onetable usage:

onetable outstanding # List migrations yet to be applied
onetable named # List available named migrations
onetable repeat # Repeat the last migration
onetable reset # Reset the database with latest migration
onetable reset # Reset the database to the latest schema
onetable status # Show most recently applied migration
onetable up # Apply the next migration
onetable NAME # Apply a named migration
Options:
--arn # Lambda ARN for migration controller
--aws-access-key # AWS access key

@@ -87,9 +85,9 @@ --aws-region # AWS service region

--dry # Dry-run, pass params.dry to migrations.
--endpoint http://host:port # Database endpoint
--endpoint http://host:port # Database endpoint for local use.
--force # Force action without confirmation
--profile prod|qa|dev|... # Select configuration profile
--quiet # Run as quietly as possible
--table TableName # DynamoDB table name
--version # Emit version number
`;
const LATEST_VERSION = 'latest';
class CLI {

@@ -104,2 +102,3 @@ usage() {

this.aws = {};
this.table = null;
}

@@ -123,2 +122,6 @@ async init() {

}
onetable.name = this.table || onetable.name;
if (!onetable.name) {
error('Missing DynamoDB table name');
}
let location;

@@ -151,3 +154,3 @@ if (config.arn) {

this.migrate = new Migrate(onetable, {
// migrations: config.migrations,
migrations: config.migrations,
dir: config.dir,

@@ -178,6 +181,6 @@ profile: config.profile,

if (cmd == 'all') {
await this.move();
await this.run();
}
else if (cmd == 'reset') {
await this.move(LATEST_VERSION);
await this.run("reset");
}

@@ -193,5 +196,8 @@ else if (cmd == 'status') {

}
else if (args.length) {
await this.move(cmd);
else if (cmd == 'named') {
await this.named();
}
else if (cmd) {
await this.run(cmd);
}
else {

@@ -208,3 +214,12 @@ this.usage();

let version = versions.length ? versions.pop() : await this.migrate.getCurrentVersion();
version = Semver.inc(version, this.bump);
if (Semver.valid(this.bump)) {
version = this.bump;
}
else {
let newVersion = Semver.inc(version, this.bump);
if (!Semver.valid(newVersion)) {
this.error(`Cannot bump version ${version} via ${this.bump}`);
}
version = newVersion;
}
let dir = Path.resolve(this.config.dir || '.');

@@ -225,3 +240,3 @@ let path = `${dir}/${version}.js`;

async list() {
let pastMigrations = await this.migrate.findPastMigrations();
let pastMigrations = await this.migrate.getPastMigrations();
if (this.quiet) {

@@ -237,7 +252,7 @@ for (let m of pastMigrations) {

else {
print('Date Version Description');
print('Date Version Description');
}
for (let m of pastMigrations) {
let date = Dates.format(m.time, 'HH:MM:ss mmm d, yyyy');
print(`${date} ${m.version.padStart(7)} ${m.description}`);
let date = Dates.format(m.date, 'HH:MM:ss mmm d, yyyy');
print(`${date} ${m.version.padStart(20)} ${m.description}`);
}

@@ -257,7 +272,17 @@ }

}
async named() {
let list = await this.migrate.getNamedMigrations();
if (list.length == 0) {
print('none');
}
else {
for (let migration of list) {
print(`${migration}`);
}
}
}
/*
Move to the target version
*/
async move(target) {
let direction;
async run(target) {
let outstanding = await this.migrate.getOutstandingVersions();

@@ -273,12 +298,14 @@ if (!target) {

}
let pastMigrations = await this.migrate.findPastMigrations();
let current = pastMigrations.length ? pastMigrations[pastMigrations.length - 1].version : '0.0.0';
let pastMigrations = await this.migrate.getPastMigrations();
let pastVersions = pastMigrations.filter(m => Semver.valid(m.version));
let current = await this.migrate.getCurrentVersion();
let versions = [];
if (target == 'latest') {
direction = 0;
let cmd;
if (target == 'latest' || target == 'reset') {
cmd = 'reset';
pastMigrations = [];
versions = [LATEST_VERSION];
versions = ["reset"];
}
else if (target == 'repeat') {
direction = 2;
cmd = 'repeat';
let version = pastMigrations.reverse().slice(0).map(m => m.version).shift();

@@ -290,3 +317,3 @@ if (version) {

else if (target == 'up') {
direction = 1;
cmd = 'up';
if (outstanding.length == 0) {

@@ -299,4 +326,4 @@ print(`All migrations applied`);

else if (target == 'down') {
direction = -1;
let version = pastMigrations.slice(0).reverse().map(m => m.version).shift();
cmd = 'down';
let version = pastVersions.slice(0).reverse().map(m => m.version).shift();
if (version) {

@@ -306,20 +333,27 @@ versions = [version];

}
else if (Semver.compare(target, current) < 0) {
direction = -1;
if (target != '0.0.0' && !pastMigrations.find(m => m.version == target)) {
error(`Cannot find target migration ${target} in applied migrations`);
else if (Semver.valid(target)) {
if (Semver.compare(target, current) < 0) {
cmd = 'down';
if (target != '0.0.0' && !pastVersions.find(p => p == target)) {
error(`Cannot find target migration ${target} in applied migrations`);
}
versions = pastVersions.reverse().filter(v => Semver.compare(v, target) > 0);
}
versions = pastMigrations.reverse().map(m => m.version).filter(v => Semver.compare(v, target) > 0);
else {
cmd = 'up';
if (Semver.compare(target, current) <= 0) {
print('Migration already applied');
return;
}
if (!outstanding.find(v => v == target)) {
error(`Cannot find migration ${target} in outstanding migrations: ${outstanding.join(', ')}`);
}
versions = outstanding.filter(v => Semver.compare(v, current) >= 0);
versions = versions.filter(v => Semver.compare(v, target) <= 0);
}
}
else {
direction = 1;
if (Semver.compare(target, current) <= 0) {
print('Migration already applied');
return;
}
if (!outstanding.find(v => v == target)) {
error(`Cannot find migration ${target} in outstanding migrations: ${outstanding}`);
}
versions = outstanding.filter(v => Semver.compare(v, current) >= 0);
versions = versions.filter(v => Semver.compare(v, target) <= 0);
// Named version
versions = [target];
cmd = target;
}

@@ -331,7 +365,12 @@ if (versions.length == 0) {

try {
await this.confirm(versions, direction);
await this.confirm(cmd, versions);
for (let version of versions) {
let verb = ['Downgrade from', 'Reset to', 'Upgrade to', 'Repeat'][direction + 1];
let migration = await this.migrate.apply(direction, version, { dry: this.dry });
print(`${verb} "${migration.version} - ${migration.description}"`);
let migration = await this.migrate.apply(cmd, version, { dry: this.dry });
let verb = {
'down': 'Downgrade database from',
'reset': 'Reset database with',
'up': 'Upgrade database to',
'repeat': 'Repeat migration'
}[cmd] || 'Run named migration';
print(`${verb} "${version} - ${migration.description}"`);
}

@@ -346,9 +385,9 @@ current = await this.migrate.getCurrentVersion();

}
async confirm(versions, direction) {
async confirm(cmd, versions) {
if (this.force) {
return;
}
let action = ['downgrade', 'reset', 'upgrade', 'repeat'][direction + 1];
cmd = { 'up': 'upgrade', 'down': 'downgrade' }[cmd] || cmd;
let noun = versions.length > 1 ? 'changes' : 'change';
let fromto = action == 'downgrade' ? 'from' : 'to';
let fromto = cmd == 'downgrade' ? 'from' : 'to';
let target = versions[versions.length - 1];

@@ -358,4 +397,4 @@ if (this.config.profile == 'prod') {

}
print(`Confirm ${versions.length} "${action}" ${noun} ${fromto} version "${target}" for database "${this.config.onetable.name}" using profile "${this.config.profile}".`);
print(`\nMigrations to ${direction < 0 ? 'revert' : 'apply'}:`);
print(`Confirm ${versions.length} "${cmd}" ${noun} ${fromto} version "${target}" for database "${this.config.onetable.name}" using profile "${this.config.profile}".`);
print(`\nMigrations to ${cmd == 'downgrade' ? 'revert' : 'apply'}:`);
for (let version of versions) {

@@ -390,3 +429,6 @@ print(`${version}`);

let arg = argv[i];
if (arg == '--aws-access-key') {
if (arg == '--arn') {
this.arn = argv[++i];
}
else if (arg == '--aws-access-key') {
this.aws.accessKeyId = argv[++i];

@@ -434,2 +476,5 @@ }

}
else if (arg == '--table') {
this.table = argv[++i];
}
else if (arg == '--verbose' || arg == '-v') {

@@ -441,3 +486,3 @@ this.verbosity = true;

}
else if (arg[0] == '-' || arg.indexOf('-') >= 0) {
else if (arg[0] == '-') {
this.usage();

@@ -457,5 +502,9 @@ }

async getConfig() {
let migrateConfig = this.migrateConfig || 'migrate.json';
let migrateConfig = this.migrateConfig || 'migrate.json5';
if (!Fs.existsSync(migrateConfig)) {
error(`Cannot locate ${migrateConfig}`);
// LEGACY
migrateConfig = 'migrate.json';
if (!Fs.existsSync(migrateConfig)) {
error(`Cannot locate migrate.json5`);
}
}

@@ -520,7 +569,7 @@ let index, profile;

}
async apply(direction, version, params = {}) {
return await this.invoke('apply', { direction, version, params });
async apply(action, version, params = {}) {
return await this.invoke('apply', { action, version, params });
}
async findPastMigrations() {
return await this.invoke('findPastMigrations');
async getPastMigrations() {
return await this.invoke('getPastMigrations');
}

@@ -533,8 +582,11 @@ async getCurrentVersion() {

}
async invoke(action, args) {
let params = { action, config: this.config };
async getNamedMigrations(limit = Number.MAX_SAFE_INTEGER) {
return await this.invoke('getNamedMigrations');
}
async invoke(cmd, args) {
let params = { cmd, config: this.config };
if (args) {
params.args = args;
}
this.debug(`Invoke migrate proxy`, { action, args, arn: this.arn });
this.debug(`Invoke migrate proxy`, { cmd, args, arn: this.arn });
let payload = JSON.stringify(params, null, 2);

@@ -548,8 +600,8 @@ let result = await this.lambda.invoke({

if (result.StatusCode != 200) {
error(`Cannot invoke ${action}: bad status code ${result.StatusCode}`);
error(`Cannot invoke ${cmd}: bad status code ${result.StatusCode}`);
}
else if (result && result.Payload) {
result = JSON.parse(result.Payload);
if (result.errorMessage) {
error(`Cannot invoke ${action}: ${result.errorMessage}`);
if (result.error) {
error(`Cannot invoke ${cmd}: ${result.error}`);
}

@@ -561,3 +613,3 @@ else {

else {
error(`Cannot invoke ${action}: no result`);
error(`Cannot invoke ${cmd}: no result`);
}

@@ -564,0 +616,0 @@ this.debug(`Migrate proxy results`, { args, result });

{
"name": "onetable-cli",
"version": "1.2.4",
"version": "1.3.0",
"type": "module",

@@ -36,13 +36,13 @@ "description": "DynamoDB OneTable CLI",

"devDependencies": {
"@types/jest": "^28.1.6",
"@types/node": "^18.6.5",
"@types/jest": "^29.5.4",
"@types/node": "^20.6.0",
"coveralls": "^3.1.1",
"eslint": "^8.21.0",
"jest": "^28.1.3",
"ts-jest": "^28.0.7",
"typescript": "^4.7.4"
"eslint": "^8.49.0",
"jest": "^29.6.4",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2"
},
"dependencies": {
"aws-sdk": "^2.1190.0",
"dynamodb-onetable": "^2.4",
"aws-sdk": "^2.1455.0",
"dynamodb-onetable": "^2.7",
"js-blend": "./src/paks/js-blend",

@@ -53,6 +53,6 @@ "js-clone": "./src/paks/js-clone",

"js-file": "./src/paks/js-file",
"json5": "^2.2.1",
"onetable-migrate": "^1.1.6",
"json5": "^2.2.3",
"onetable-migrate": "^1.2.0",
"readline": "^1.3.0",
"semver": "^7.3.7",
"semver": "^7.5.4",
"senselogs": "^1"

@@ -59,0 +59,0 @@ },

@@ -12,3 +12,3 @@ ![OneTable](https://www.sensedeep.com/images/ring-short.png?renew)

The CLI is ideal for development teams to initialize and reset database contents and for production use to control and sequence step-wise database upgrades and downgrades. It is a vital tool to successfully evolve your Single-Table DynamoDB patterns.
The CLI is ideal for development teams to initialize and reset database contents and for production use to control and sequence step-wise database upgrades, downgrades and maintenance tasks. It is a vital tool to successfully evolve your Single-Table DynamoDB patterns.

@@ -21,5 +21,7 @@ The OneTable CLI was used in production by the [SenseDeep Developer Studio](https://www.sensedeep.com/) for all DynamoDB access for a year before it was published as an NPM module.

* Mutates database schema and contents via discrete, reversible migrations.
* Migrate upwards, downwards, to specific versions.
* Migrate upwards, downwards, and to specific versions.
* Automated, ordered sequencing of migrations in both directions.
* Operates on local databases, remote databases via AWS credentials and via a Lambda proxy.
* Named migrations for database maintenance, auditing and other tasks.
* Operates on local databases and remote databases.
* Use AWS credentials or profiles.
* Add and remove seed data in any migration.

@@ -34,4 +36,2 @@ * Quick reset of DynamoDB databases for development.

NOTE: this package requires NPM version 7.0 or later. The version 6.x of NPM that comes with Node v14 will not work as it does not support local packages.
```sh

@@ -60,3 +60,3 @@ npm i onetable-cli -g

Then create a `migrate.json` with your DynamoDB OneTable configuration. We use JSON5 so you can use Javascript object literal syntax.
Then create a `migrate.json5` with your DynamoDB OneTable configuration. We use JSON5 so you can use Javascript object literal syntax.

@@ -68,26 +68,26 @@ ```javascript

// Other onetable configuration parameters.
}
partial: true,
},
dir: './migrations'
}
```
Set the `name` property to the name of your DynamoDB table.
Set the `name` property to the name of your DynamoDB table and set the `dir` property to point to the directory containing the migrations.
If you need to have your migrations in a different directory, you can set the migrate.json `dir` property to point to the directory containing the migrations themselves.
You pass your OneTable configuration via the `onetable` collection. Ensure your `crypto`, `nulls` and `typeField` settings match your deployed code. If you have these set to non-default settings in your code, add them to your migrate.json5 `onetable` map to match.
You pass your OneTable configuration via the `onetable` collection. Ensure your `crypto`, `delimiter`, `nulls` and `typeField` settings match your deployed code. If you have these set to non-default settings in your code, add them to your migrate.json `onetable` map to match.
**Generate a stub migration**
Generate a stub migration
Migrations are Javascript files that export the methods `up` and `down` to apply the migration and a `description` property. The migration must nominate a version and provide the OneTable schema that applies for the table data at this version level.
```sh
cd ./migrations
onetable generate migration
```
This will create a `0.0.1.js` migration that contains the following. Edit the `up` and `down` methods and description to suit.
This will create a `0.0.1.js` migration that contains an `up` method to upgrade the database and a `down` method to downgrade to the previous version. Customize the `up` and `down` methods and description to suit.
The `db` property is the OneTable `Table` instance. This `migrate` property is an instance of the CLI Migrate class.
For example:
```javascript
import Schema from 'your-onetable-schema',
export default {

@@ -99,3 +99,3 @@ version: '0.0.1',

if (!params.dry) {
await db.create('Model', {})
// Code here to upgrade the database
} else {

@@ -107,3 +107,3 @@ console.log('Dry run: create "Model"')

if (!params.dry) {
await db.remove('Model', {})
// Code here to downgrade the database to the prior version
} else {

@@ -116,4 +116,6 @@ console.log('Dry run: remove "Model"')

### Examples
The `db` property is the OneTable `Table` instance. This `migrate` property is an instance of the CLI Migrate class.
### OneTable Comamnds
Apply the next migration.

@@ -143,2 +145,9 @@

Run a specific named migration.
```sh
onetable cleanup-orphans
onetable reset
```
Apply all outstanding migrations.

@@ -168,3 +177,3 @@

Reset the database to the latest migration. This should reset the database and apply the `latest.js` migration. The purpose of the `latest` migration is to have one migration that can quickly create a new database with the latest schema without having to apply all historical migrations.
Reset the database to the latest version. If you provide a `reset.js` migration, this migrations should reset the database to a known good state. The purpose of the `reset` migration is to have one migration that can quickly initialize a database with the latest data and schema without having to apply all historical migrations.

@@ -179,6 +188,9 @@ ```sh

onetable --bump 2.4.3 generate
# or generate with a bumped minor version number
onetable --bump minor generate
```
Do a dry run for a migration and not execute. This will set params.dry to true when invoking the up/down.
It is up to the up/down routines to implement the dry run functionality if that support is desired.
Do a dry run for a migration and not execute. This will set params.dry to true when invoking the up/down migration function. It is up to the up/down routines to implement the dry run functionality if that support is desired. During a dry run, the database migration table will not be updated nor will the current version and schema.

@@ -195,4 +207,4 @@ ```sh

--aws-secret-key # AWS secret key
--bump [major,minor,patch] # Version digit to bump in generation
--config ./migrate.json # Migration configuration
--bump [VERSION|major|minor|patch] # Version to generate or digit to bump
--config ./migrate.json5 # Migration configuration file
--crypto cipher:password # Crypto to use for encrypted attributes

@@ -205,6 +217,7 @@ --dir directory # Change to directory to execute

--quiet # Run as quietly as possible
--table TableName # Set the DynamoDB table name
--version # Emit version number
```
### Accessing AWS
### Authenticating with DynamoDB

@@ -214,14 +227,13 @@ You can configure access to your DynamoDB table in your AWS account several ways:

* via command line options
* via the migrate.json
* via the migrate.json5
* via environment variables
* via proxy
Via command line option:
```
```shell
onetable --aws-access-key key --aws-secret-key secret --aws-region us-east-1
```
Via migrate.json
```
Via migrate.json5:
```javascript
{

@@ -238,3 +250,3 @@ aws: {

```
```bash
export AWS_ACCESS_KEY_ID=your-access-key

@@ -246,3 +258,3 @@ export AWS_SECRET_ACCESS_KEY=your-secret-key

You can also use:
```
```bash
export AWS_PROFILE=aws-profile-name

@@ -252,5 +264,5 @@ export AWS_REGION=us-east-1

To access a local DynamoDB database, set the migrate.json `aws.endpoint` property to point to the listening endpoint.
To access a local DynamoDB database, set the migrate.json5 `aws.endpoint` property to point to the listening endpoint.
```
```javascript
{

@@ -263,6 +275,5 @@ aws: {

To communicate with a Lambda hosting the [OneTable Migrate Library](), set the `arn` field to the ARN of your Lambda function.
Then define your AWS credentials as described above to grant access for the CLI to your Lambda.
To communicate with a Lambda hosting the [OneTable Migrate Library](), set the `arn` field to the ARN of your Lambda function. Then define your AWS credentials as described above to grant access for the CLI to your Lambda.
```
```javascript
{

@@ -280,21 +291,23 @@ arn: 'arn:aws:lambda:us-east-1:123456789012:function:migrate-prod-invoke'

When deployed, configure migrations by setting the CLI migrate.json `arn` property to the ARN of your migration Lambda that hosts the Migration Library.
When deployed, configure migrations by setting the CLI migrate.json5 `arn` property to the ARN of your migration Lambda that hosts the Migration Library.
### Latest Migration
### Reset Migration
You can create a special `latest` migration that is used for the `migrate reset` command which is is a quick way to get a development database up to the current version.
You can create a special named `reset` migration that is used for the `onetable reset` command which is is a quick way to get a development database up to the current version.
The latest migration should remove all data from the database and then initialize the database equivalent to applying all migrations.
The `reset` migration should remove all data from the database and then initialize the database as required.
When creating your `latest.js` migration, be very careful when removing all items from the database. We typically protect this with a test against the deployment profile to ensure you never do this on a production database.
When creating your `reset.js` migration, be very careful when removing all items from the database. We typically protect this with a test against the deployment profile to ensure you never do this on a production database.
Sample latest.js migration:
Sample reset.js migration:
```javascript
import Schema from 'your-onetable-schema.js'
export default {
version: '0.0.1',
description: 'Database reset to latest version',
description: 'Database reset',
schema: Schema,
async up(db, migrate, params) {
// Careful not to remove all items on a production database!
if (migrate.params.profile == 'dev') {

@@ -311,2 +324,3 @@ await removeAllItems(db)

}
async function removeAllItems(db) {

@@ -324,22 +338,23 @@ do {

You can use profiles in your `migrate.json` to have specific configuration for different build profiles.
You can use profiles in your `migrate.json5` to have specific configuration for different build profiles.
Profiles are implemented by copying the properties from the relevant `profile.NAME` collection to the top level. For example:
Here is a sample migrate.json with profiles:
Here is a sample migrate.json5 with profiles:
```javascript
{
onetable: {
name: 'sensedb',
partial: true,
},
profiles: {
dev: {
dir: './migrations',
name: 'sensedb',
endpoint: 'http://localhost:8000'
},
qa: {
name: 'sensedb',
arn: 'arn:aws:lambda:us-east-1:xxxx:function:migrate-qa-invoke'
},
prod: {
name: 'sensedb',
arn: 'arn:aws:lambda:us-east-1:xxxx:function:migrate-prod-invoke'

@@ -346,0 +361,0 @@ }

@@ -7,3 +7,3 @@ #!/usr/bin/env node

Migrations:
onetable [all, down, generate, list, outstanding, repeat, reset, status, up, N.N.N]
onetable [all, down, generate, list, named, outstanding, repeat, reset, status, up, N.N.N, NAMED]

@@ -15,8 +15,6 @@ Reads migrate.json:

},
delimiter: ':',
dir: './migrations-directory',
hidden: false,
name: 'table-name',
nulls: false,
typeField: 'type',
onetable: {
name: 'table-name',
},
aws: {accessKeyId, secretAccessKey, region},

@@ -42,10 +40,12 @@ arn: 'lambda-arn'

// import SenseLogs from '../../senselogs/dist/mjs/index.js'
const MigrationTemplate = `
import Schema from 'your-onetable-schema'
const MigrationTemplate = `
export default {
version: 'VERSION',
schema: Schema,
description: 'Purpose of this migration',
async up(db, migrate, params) {
if (!params.dry) {
// db.log.info('Running upgrade')
// await db.create('Model', {})

@@ -61,9 +61,2 @@ }

const Types = {
String: 'string',
Number: 'number',
Boolean: 'boolean',
String: 'string',
}
const Usage = `

@@ -79,8 +72,11 @@ onetable usage:

onetable outstanding # List migrations yet to be applied
onetable named # List available named migrations
onetable repeat # Repeat the last migration
onetable reset # Reset the database with latest migration
onetable reset # Reset the database to the latest schema
onetable status # Show most recently applied migration
onetable up # Apply the next migration
onetable NAME # Apply a named migration
Options:
--arn # Lambda ARN for migration controller
--aws-access-key # AWS access key

@@ -95,11 +91,10 @@ --aws-region # AWS service region

--dry # Dry-run, pass params.dry to migrations.
--endpoint http://host:port # Database endpoint
--endpoint http://host:port # Database endpoint for local use.
--force # Force action without confirmation
--profile prod|qa|dev|... # Select configuration profile
--quiet # Run as quietly as possible
--table TableName # DynamoDB table name
--version # Emit version number
`
const LATEST_VERSION = 'latest'
class CLI {

@@ -115,2 +110,3 @@ usage() {

this.aws = {}
this.table = null
}

@@ -137,2 +133,6 @@

}
onetable.name = this.table || onetable.name
if (!onetable.name) {
error('Missing DynamoDB table name')
}

@@ -165,3 +165,3 @@ let location

this.migrate = new Migrate(onetable, {
// migrations: config.migrations,
migrations: config.migrations,
dir: config.dir,

@@ -192,5 +192,5 @@ profile: config.profile,

if (cmd == 'all') {
await this.move()
await this.run()
} else if (cmd == 'reset') {
await this.move(LATEST_VERSION)
await this.run("reset")
} else if (cmd == 'status') {

@@ -202,4 +202,6 @@ await this.status()

await this.outstanding()
} else if (args.length) {
await this.move(cmd)
} else if (cmd == 'named') {
await this.named()
} else if (cmd) {
await this.run(cmd)
} else {

@@ -216,3 +218,11 @@ this.usage()

let version = versions.length ? versions.pop() : await this.migrate.getCurrentVersion()
version = Semver.inc(version, this.bump)
if (Semver.valid(this.bump)) {
version = this.bump
} else {
let newVersion = Semver.inc(version, this.bump)
if (!Semver.valid(newVersion)) {
this.error(`Cannot bump version ${version} via ${this.bump}`)
}
version = newVersion
}
let dir = Path.resolve(this.config.dir || '.')

@@ -234,3 +244,3 @@ let path = `${dir}/${version}.js`

async list() {
let pastMigrations = await this.migrate.findPastMigrations()
let pastMigrations = await this.migrate.getPastMigrations()
if (this.quiet) {

@@ -244,7 +254,7 @@ for (let m of pastMigrations) {

} else {
print('Date Version Description')
print('Date Version Description')
}
for (let m of pastMigrations) {
let date = Dates.format(m.time, 'HH:MM:ss mmm d, yyyy')
print(`${date} ${m.version.padStart(7)} ${m.description}`)
let date = Dates.format(m.date, 'HH:MM:ss mmm d, yyyy')
print(`${date} ${m.version.padStart(20)} ${m.description}`)
}

@@ -265,7 +275,17 @@ }

async named() {
let list = await this.migrate.getNamedMigrations()
if (list.length == 0) {
print('none')
} else {
for (let migration of list) {
print(`${migration}`)
}
}
}
/*
Move to the target version
*/
async move(target) {
let direction
async run(target) {
let outstanding = await this.migrate.getOutstandingVersions()

@@ -281,13 +301,15 @@

}
let pastMigrations = await this.migrate.findPastMigrations()
let current = pastMigrations.length ? pastMigrations[pastMigrations.length - 1].version : '0.0.0'
let pastMigrations = await this.migrate.getPastMigrations()
let pastVersions = pastMigrations.filter(m => Semver.valid(m.version))
let current = await this.migrate.getCurrentVersion()
let versions = []
let cmd
if (target == 'latest') {
direction = 0
if (target == 'latest' || target == 'reset') {
cmd = 'reset'
pastMigrations = []
versions = [LATEST_VERSION]
versions = ["reset"]
} else if (target == 'repeat') {
direction = 2
cmd = 'repeat'
let version = pastMigrations.reverse().slice(0).map(m => m.version).shift()

@@ -299,3 +321,3 @@ if (version) {

} else if (target == 'up') {
direction = 1
cmd = 'up'
if (outstanding.length == 0) {

@@ -308,4 +330,4 @@ print(`All migrations applied`)

} else if (target == 'down') {
direction = -1
let version = pastMigrations.slice(0).reverse().map(m => m.version).shift()
cmd = 'down'
let version = pastVersions.slice(0).reverse().map(m => m.version).shift()
if (version) {

@@ -315,20 +337,26 @@ versions = [version]

} else if (Semver.compare(target, current) < 0) {
direction = -1
if (target != '0.0.0' && !pastMigrations.find(m => m.version == target)) {
error(`Cannot find target migration ${target} in applied migrations`)
} else if (Semver.valid(target)) {
if (Semver.compare(target, current) < 0) {
cmd = 'down'
if (target != '0.0.0' && !pastVersions.find(p => p == target)) {
error(`Cannot find target migration ${target} in applied migrations`)
}
versions = pastVersions.reverse().filter(v => Semver.compare(v, target) > 0)
} else {
cmd = 'up'
if (Semver.compare(target, current) <= 0) {
print('Migration already applied')
return
}
if (!outstanding.find(v => v == target)) {
error(`Cannot find migration ${target} in outstanding migrations: ${outstanding.join(', ')}`)
}
versions = outstanding.filter(v => Semver.compare(v, current) >= 0)
versions = versions.filter(v => Semver.compare(v, target) <= 0)
}
versions = pastMigrations.reverse().map(m => m.version).filter(v => Semver.compare(v, target) > 0)
} else {
direction = 1
if (Semver.compare(target, current) <= 0) {
print('Migration already applied')
return
}
if (!outstanding.find(v => v == target)) {
error(`Cannot find migration ${target} in outstanding migrations: ${outstanding}`)
}
versions = outstanding.filter(v => Semver.compare(v, current) >= 0)
versions = versions.filter(v => Semver.compare(v, target) <= 0)
// Named version
versions = [target]
cmd = target
}

@@ -340,7 +368,12 @@ if (versions.length == 0) {

try {
await this.confirm(versions, direction)
await this.confirm(cmd, versions)
for (let version of versions) {
let verb = ['Downgrade from', 'Reset to', 'Upgrade to', 'Repeat'][direction + 1]
let migration = await this.migrate.apply(direction, version, {dry: this.dry})
print(`${verb} "${migration.version} - ${migration.description}"`)
let migration = await this.migrate.apply(cmd, version, {dry: this.dry})
let verb = {
'down': 'Downgrade database from',
'reset': 'Reset database with',
'up': 'Upgrade database to',
'repeat': 'Repeat migration'
}[cmd] || 'Run named migration'
print(`${verb} "${version} - ${migration.description}"`)
}

@@ -356,9 +389,9 @@ current = await this.migrate.getCurrentVersion()

async confirm(versions, direction) {
async confirm(cmd, versions) {
if (this.force) {
return
}
let action = ['downgrade', 'reset', 'upgrade', 'repeat'][direction + 1]
cmd = { 'up': 'upgrade', 'down': 'downgrade'}[cmd] || cmd
let noun = versions.length > 1 ? 'changes' : 'change'
let fromto = action == 'downgrade' ? 'from' : 'to'
let fromto = cmd == 'downgrade' ? 'from' : 'to'
let target = versions[versions.length - 1]

@@ -368,4 +401,4 @@ if (this.config.profile == 'prod') {

}
print(`Confirm ${versions.length} "${action}" ${noun} ${fromto} version "${target}" for database "${this.config.onetable.name}" using profile "${this.config.profile}".`)
print(`\nMigrations to ${direction < 0 ? 'revert' : 'apply'}:`)
print(`Confirm ${versions.length} "${cmd}" ${noun} ${fromto} version "${target}" for database "${this.config.onetable.name}" using profile "${this.config.profile}".`)
print(`\nMigrations to ${cmd == 'downgrade' ? 'revert' : 'apply'}:`)
for (let version of versions) {

@@ -402,3 +435,5 @@ print(`${version}`)

let arg = argv[i]
if (arg == '--aws-access-key') {
if (arg == '--arn') {
this.arn = argv[++i]
} else if (arg == '--aws-access-key') {
this.aws.accessKeyId = argv[++i]

@@ -432,2 +467,4 @@ } else if (arg == '--aws-secret-key') {

this.quiet = true
} else if (arg == '--table') {
this.table = argv[++i]
} else if (arg == '--verbose' || arg == '-v') {

@@ -437,3 +474,3 @@ this.verbosity = true

await this.printVersion()
} else if (arg[0] == '-' || arg.indexOf('-') >= 0) {
} else if (arg[0] == '-') {
this.usage()

@@ -453,5 +490,9 @@ } else {

async getConfig() {
let migrateConfig = this.migrateConfig || 'migrate.json'
let migrateConfig = this.migrateConfig || 'migrate.json5'
if (!Fs.existsSync(migrateConfig)) {
error(`Cannot locate ${migrateConfig}`)
// LEGACY
migrateConfig = 'migrate.json'
if (!Fs.existsSync(migrateConfig)) {
error(`Cannot locate migrate.json5`)
}
}

@@ -522,8 +563,8 @@ let index, profile

async apply(direction, version, params = {}) {
return await this.invoke('apply', {direction, version, params})
async apply(action, version, params = {}) {
return await this.invoke('apply', {action, version, params})
}
async findPastMigrations() {
return await this.invoke('findPastMigrations')
async getPastMigrations() {
return await this.invoke('getPastMigrations')
}

@@ -539,8 +580,12 @@

async invoke(action, args) {
let params = {action, config: this.config}
async getNamedMigrations(limit = Number.MAX_SAFE_INTEGER) {
return await this.invoke('getNamedMigrations')
}
async invoke(cmd, args) {
let params = {cmd, config: this.config}
if (args) {
params.args = args
}
this.debug(`Invoke migrate proxy`, {action, args, arn: this.arn})
this.debug(`Invoke migrate proxy`, {cmd, args, arn: this.arn})

@@ -557,8 +602,8 @@ let payload = JSON.stringify(params, null, 2)

if (result.StatusCode != 200) {
error(`Cannot invoke ${action}: bad status code ${result.StatusCode}`)
error(`Cannot invoke ${cmd}: bad status code ${result.StatusCode}`)
} else if (result && result.Payload) {
result = JSON.parse(result.Payload)
if (result.errorMessage) {
error(`Cannot invoke ${action}: ${result.errorMessage}`)
if (result.error) {
error(`Cannot invoke ${cmd}: ${result.error}`)
} else {

@@ -568,3 +613,3 @@ result = result.body

} else {
error(`Cannot invoke ${action}: no result`)
error(`Cannot invoke ${cmd}: no result`)
}

@@ -571,0 +616,0 @@ this.debug(`Migrate proxy results`, {args, result})

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc