diff --git a/README.md b/README.md index d999553..1a6470b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ts-migrate-mongoose -A node/typescript based migration framework for mongoose +A node, typescript based migration framework for mongoose [![NPM version](https://badge.fury.io/js/ts-migrate-mongoose.svg)](http://badge.fury.io/js/ts-migrate-mongoose) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ilovepixelart_ts-migrate-mongoose&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ilovepixelart_ts-migrate-mongoose) @@ -12,7 +12,7 @@ A node/typescript based migration framework for mongoose ## Motivation -ts-migrate-mongoose is a migration framework for projects which are already using mongoose. +ts-migrate-mongoose is a migration framework for projects which are already using mongoose **Most other migration frameworks:** @@ -25,65 +25,73 @@ ts-migrate-mongoose is a migration framework for projects which are already usin - Stores migration state in MongoDB - Provides plenty of features such as - Access to mongoose models in migrations - - Use of promises or standard callbacks - - custom config files or env variables for migration options - - ability to delete unused migrations + - Use of promises + - Custom config files `migrate.json` / `migrate.ts` or `.env` file for migration options + - Ability to delete unused migrations - Relies on a simple *GLOBAL* state of whether or not each migration has been called -### Getting Started with the CLI +## Getting Started with the CLI -Install ts-migrate-mongoose locally in you project +- Locally inside your project ```bash npm install ts-migrate-mongoose +npm exec migrate [command] [options] # or yarn add ts-migrate-mongoose -``` - -And then run - -```bash yarn migrate [command] [options] -# or -npm exec migrate [command] [options] ``` -Install it globally +- Install it globally ```bash npm install -g ts-migrate-mongoose +migrate [command] [options] # or yarn global add ts-migrate-mongoose -``` - -and then run - -```bash migrate [command] [options] ``` -Full details about commands and options can be found by running +- Full details about commands and options can be found by running ```bash -# yarn yarn migrate help - -# npm +# or npm exec migrate help ``` -### Examples +```text +CLI migration tool for mongoose + +Options: + -f, --config-path path to the config file (default: "migrate") + -d, --uri mongo connection string + -c, --collection collection name to use for the migrations (default: "migrations") + -a, --autosync automatically sync new migrations without prompt (default: false) + -m, --migrations-path path to the migration files (default: "./migrations") + -t, --template-path template file to use when creating a migration + -cd, --change-dir change current working directory before running anything + -h, --help display help for command + +Commands: + list list all migrations + create create a new migration file + up [migration-name] run all migrations or a specific migration if name provided + down roll back migrations down to given name + prune delete extraneous migrations from migration folder or database + help [command] display help for command +``` + +- More examples ```bash -# yarn yarn migrate list -d mongodb://localhost/my-db yarn migrate create add_users -d mongodb://localhost/my-db yarn migrate up add_user -d mongodb://localhost/my-db yarn migrate down delete_names -d mongodb://localhost/my-db yarn migrate prune -d mongodb://localhost/my-db yarn migrate list --config settings.json - -# npm +# or npm exec migrate list -d mongodb://localhost/my-db npm exec migrate create add_users -d mongodb://localhost/my-db npm exec migrate up add_user -d mongodb://localhost/my-db @@ -92,81 +100,63 @@ npm exec migrate prune -d mongodb://localhost/my-db npm exec migrate list --config settings.json ``` -### Setting Options Automatically - -If you want to not provide the options such as `--connectionString` to the program every time you have 2 options. +## Setting Options Automatically -#### 1. Set the option as an Environment Variable in two formats +If you don't want to provide `-d --uri` to the program every time you have 2 options. -- UPPERCASE +### 1. Set the options using environment variables in two formats ```bash - export MIGRATE_CONNECTION_STRING=mongodb://localhost/my-db - export MIGRATE_TEMPLATE_PATH=migrations/template.ts + export MIGRATE_MONGO_URI=mongodb://localhost/my-db + export MIGRATE_MONGO_COLLECTION=migrations export MIGRATE_MIGRATIONS_PATH=migrations - export MIGRATE_COLLECTION=migrations + export MIGRATE_TEMPLATE_PATH=migrations/template.ts export MIGRATE_AUTOSYNC=true - ``` - -- camelCase - - ```bash - export migrateConnectionString=mongodb://localhost/my-db - export migrateTemplatePath=migrations/template.ts + # or + export migrateMongoUri=mongodb://localhost/my-db + export migrateMongoCollection=migrations export migrateMigrationsPath=migrations - export migrateCollection=migrations + export migrateTemplatePath=migrations/template.ts export migrateAutosync=true ``` -#### 2. Environment `.env` files are also supported. All variables will be read from the `.env` file and set by ts-migrate-mongoose - -- UPPERCASE +### 2. Environment `.env` files are also supported. All variables will be read from the `.env` file and set by ts-migrate-mongoose ```bash - MIGRATE_CONNECTION_STRING=mongodb://localhost/my-db - MIGRATE_TEMPLATE_PATH=migrations/template.ts - MIGRATE_MIGRATIONS_PATH=migrations - MIGRATE_COLLECTION=migrations - MIGRATE_AUTOSYNC=true + MIGRATE_MONGO_URI=mongodb://localhost/my-db + ... + # or + migrateMongoUri=mongodb://localhost/my-db + ... ``` -- camelCase - - ```bash - migrateConnectionString=mongodb://localhost/my-db - migrateTemplatePath=migrations/template.ts - migrateMigrationsPath=migrations - migrateCollection=migrations - migrateAutosync=true - ``` - -#### 2. Provide a config file (defaults to *migrate.json* or *migrate.ts*) +### 3. Provide a config file (defaults to *migrate.json* or *migrate.ts*) ```bash # If you have migrate.ts or migrate.json in the directory, you don't need to do anything yarn migrate list - +# or npm exec migrate list # Otherwise you can provide a config file yarn migrate list --config somePath/myCustomConfigFile[.json] - +# or npm exec migrate list --config somePath/myCustomConfigFile[.json] ``` -#### Options Override Order +## Options Override Order Command line args *beat* Env vars *beats* Config File Just make sure you don't have aliases of the same option with 2 different values between env vars and config file -### Migration Files +## Migration Files By default, ts-migrate-mongoose assumes your migration folder exists. Here's an example of a migration created using `migrate create some-migration-name` . This example demonstrates how you can access your `mongoose` models and handle errors in your migrations -#### 1662715725041-first-migration-demo.ts +- 1662715725041-first-migration-demo.ts ```typescript /** @@ -184,7 +174,7 @@ export async function down () { } ``` -### Access to mongoose models in your migrations +## Access to mongoose models in your migrations Just go about your business as usual, importing your models and making changes to your database. @@ -192,7 +182,7 @@ ts-migrate-mongoose makes an independent connection to MongoDB to fetch and writ Below is an example of a typical setup in a mongoose project -#### models/User.ts +- models/User.ts ```typescript import { Schema, model } from 'mongoose' @@ -216,17 +206,17 @@ const UserSchema = new Schema({ export default model('user', UserSchema) ``` -#### Back to migration file 1662715725041-first-migration-demo.ts +- 1662715725041-first-migration-demo.ts ```typescript import User from '../models/User' export async function up() { // Then you can use it in the migration like so - await User.create({ firstName: 'Ada', lastName: 'Lovelace' }); + await User.create({ firstName: 'Ada', lastName: 'Lovelace' }) // Or do something such as - const users = await User.find(); + const users = await User.find() /* Do something with users */ } ``` @@ -237,13 +227,13 @@ If you're using the package programmatically. You can access your models using t export async function up() { // "this('user')" is the same as calling "connection.model('user')" // using the connection you passed to the Migrator constructor. - await this('user').create({ firstName: 'Ada', lastName: 'Lovelace' }); + await this('user').create({ firstName: 'Ada', lastName: 'Lovelace' }) } ``` -### Notes +## Notes -Currently, the **-d**/**connectionString** must include the database to use for migrations in the uri. +Currently, the **-d**/**uri** must include the database to use for migrations in the uri. example: `-d mongodb://localhost:27017/development` . @@ -251,7 +241,7 @@ If you don't want to pass it in every time feel free to use the `migrate.json` c Feel Free to check out the examples in the project to get a better idea of usage -### How to contribute +## How to contribute 1. Start an issue. We will discuss the best approach 2. Make a pull request. I'll review it and comment until we are both confident about it diff --git a/examples/command-line/README.md b/examples/command-line/README.md index 37aabf7..915c39e 100644 --- a/examples/command-line/README.md +++ b/examples/command-line/README.md @@ -1,4 +1,4 @@ -# Example of using the CLI +# Example for CLI tool After running `npm install ts-migrate-mongoose`, you will have the migration binary available to you as `./node_modules/.bin/migrate`. @@ -10,9 +10,9 @@ You can simply create a new migration (e.g. `my_new_migration`) by running ./node_modules/.bin/migrate create my_new_migration ``` -where `` must at a MINIMUM contain the database url (using the `-d`/`--connectionString` option). +where `` must at a MINIMUM contain the database url (using the `-d`/`--uri` option). -### Listing Migrations +## Listing Migrations This shows you the migrations with their current states. @@ -23,7 +23,7 @@ This shows you the migrations with their current states. ./node_modules/.bin/migrate list ``` -#### Running a Migration (Migrate up) +## Running a Migration (Migrate up) Let's say your `migrate list` command shows @@ -55,7 +55,7 @@ up: 1463003345598-add_processed_credit_cards.ts up: 1463603842010-add_default_regional_settings.ts ``` -##### Undoing Migrations (Migrate down) +## Undoing Migrations (Migrate down) What if you want to undo the previous step? @@ -77,7 +77,7 @@ down: 1463003345598-add_processed_credit_cards.ts down: 1463603842010-add_default_regional_settings.ts ``` -##### Synchronizing Your DB with new Migrations +## Synchronizing Your DB with new Migrations Lets say you `git pull` the latest changes from your project and someone had made a new migration called `add_unicorns` which adds much requested unicorns to your app. @@ -102,6 +102,6 @@ Once imported, the default state is down so you'll have to `migrate up add_unico and you'll be prompted to remove it from the **FILE SYSTEM**. -###### But what happens if I want to sync automatically instead of doing this every time? +## But what happens if I want to sync automatically instead of doing this every time? just add the option `--autosync` and the migrate command will automatically import new migrations in your migrations folder before running commands. diff --git a/examples/config-file-usage/README.md b/examples/config-file-usage/README.md index 5d66297..c3b163c 100644 --- a/examples/config-file-usage/README.md +++ b/examples/config-file-usage/README.md @@ -10,9 +10,9 @@ Directory in this example is **not** the default `./migrations` but `./my-custom ```bash # If you use yarn -yarn migrate --migrationsDir my-custom/migrations -d mongodb://localhost/my-db create my_new_migration +yarn migrate --migrations-path my-custom/migrations -d mongodb://localhost/my-db create my_new_migration # If you use npm -npm exec migrate --migrationsDir my-custom/migrations -d mongodb://localhost/my-db create my_new_migration +npm exec migrate --migrations-path my-custom/migrations -d mongodb://localhost/my-db create my_new_migration ``` We can simply run diff --git a/examples/config-file-usage/migrate.json b/examples/config-file-usage/migrate.json index e8bcf70..9e50784 100644 --- a/examples/config-file-usage/migrate.json +++ b/examples/config-file-usage/migrate.json @@ -1,4 +1,4 @@ { - "connectionString": "mongodb://localhost/my-db", - "migrationsDir": "migrations" + "uri": "mongodb://localhost/my-db", + "migrationsPath": "migrations" } diff --git a/examples/config-file-usage/migrate.ts b/examples/config-file-usage/migrate.ts index 18bf34f..058869f 100644 --- a/examples/config-file-usage/migrate.ts +++ b/examples/config-file-usage/migrate.ts @@ -1,4 +1,4 @@ export default { - connectionString: 'mongodb://localhost/my-db', - migrationsDir: 'migrations' + uri: 'mongodb://localhost/my-db', + migrationsPath: 'migrations' } diff --git a/examples/programmatic-usage/README.md b/examples/programmatic-usage/README.md index e32b076..5f02f92 100644 --- a/examples/programmatic-usage/README.md +++ b/examples/programmatic-usage/README.md @@ -4,33 +4,36 @@ import Migrator from 'ts-migrate-mongoose' // Define all your variables -const migrationsDir = '/path/to/migrations/' -const templatePath -const dbUrl = 'mongodb://localhost/db' const collection = 'myMigrations' const autosync = true const migrator = new Migrator({ - migrationsPath: migrationsDir, // Path to migrations directory - templatePath: templatePath, // The template to use when creating migrations needs up and down functions exposed - connectionString: dbUrl, // mongo url - collection: collection, // collection name to use for migrations (defaults to 'migrations') - autosync: autosync // if making a CLI app, set this to false to prompt the user, otherwise true -}); - - -const migrationName = 'my-migration'; + // Path to migrations directory, default is ./migrations + migrationsPath: '/path/to/migrations/', + // The template to use when creating migrations needs up and down functions exposed + // No need to specify unless you want to use a custom template + templatePath: '/path/to/template.ts', + // MongoDB connection string URI + uri: 'mongodb://localhost/my-db', + // Collection name to use for migrations (defaults to 'migrations') + collection: 'migrations', + // Ff making a CLI app, set this to false to prompt the user, otherwise true + autosync: true +}) + + +const migrationName = 'my-migration-name' // Create a new migration await migrator.create(migrationName).then(() => { - console.log(`Migration created. Run `+ `mongoose-migrate up ${migrationName}`.cyan + ` to apply the migration.`); -}); + console.log(`Migration created. Run `+ `migrate up ${migrationName}`.cyan + ` to apply the migration`) +}) // Migrate Up -await migrator.run('up', migrationName); +await migrator.run('up', migrationName) // Migrate Down -await migrator.run('down', migrationName); +await migrator.run('down', migrationName) // List Migrations /* @@ -41,13 +44,12 @@ Promise which resolves with { name: 'my-migration', filename: '149213223424_my-migration.ts', state: 'up' }, { name: 'add-cows', filename: '149213223453_add-cows.ts', state: 'down' } ] - */ -await migrator.list(); +await migrator.list() // Prune extraneous migrations from file system -await migrator.prune(); +await migrator.prune() // Synchronize DB with latest migrations from file system /* @@ -56,5 +58,5 @@ on the file system but missing in the database into the database This functionality is opposite of prune() */ -await migrator.sync(); +await migrator.sync() ``` diff --git a/package-lock.json b/package-lock.json index 8db3201..a29dd86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,11 @@ "license": "MIT", "dependencies": { "colors": "^1.4.0", + "commander": "^9.4.0", "dotenv": "^16.0.2", "inquirer": "^8.2.3", "mongoose": "^6.6.0", - "ts-node": "^10.9.1", - "yargs": "^17.5.1" + "ts-node": "^10.9.1" }, "bin": { "migrate": "dist/cjs/bin.js" @@ -2624,6 +2624,7 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -2683,6 +2684,14 @@ "node": ">=0.1.90" } }, + "node_modules/commander": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -3094,6 +3103,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4092,6 +4102,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7076,6 +7087,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8321,6 +8333,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -8337,6 +8350,7 @@ "version": "17.5.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^7.0.2", @@ -8355,6 +8369,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -10279,6 +10294,7 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -10320,6 +10336,11 @@ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, + "commander": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -10628,7 +10649,8 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", @@ -11307,7 +11329,8 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-intrinsic": { "version": "1.1.2", @@ -13388,7 +13411,8 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true }, "resolve": { "version": "1.22.1", @@ -14221,7 +14245,8 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true }, "yallist": { "version": "4.0.0", @@ -14233,6 +14258,7 @@ "version": "17.5.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -14246,7 +14272,8 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true }, "yauzl": { "version": "2.10.0", diff --git a/package.json b/package.json index c9d072e..7bd3542 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,11 @@ }, "dependencies": { "colors": "^1.4.0", + "commander": "^9.4.0", "dotenv": "^16.0.2", "inquirer": "^8.2.3", "mongoose": "^6.6.0", - "ts-node": "^10.9.1", - "yargs": "^17.5.1" + "ts-node": "^10.9.1" }, "devDependencies": { "@shelf/jest-mongodb": "^4.1.0", @@ -78,4 +78,4 @@ "ts-node-dev": "^2.0.0", "typescript": "^4.8.3" } -} +} \ No newline at end of file diff --git a/src/bin.ts b/src/bin.ts index 8eb8fe0..81e2c9e 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -1,4 +1,4 @@ #! /usr/bin/env node -import { cli } from './cli' +import { migrate } from './commander' -export default cli() +export default migrate.run() diff --git a/src/cli.ts b/src/cli.ts deleted file mode 100755 index a6a211d..0000000 --- a/src/cli.ts +++ /dev/null @@ -1,193 +0,0 @@ -import path from 'path' -import dotenv from 'dotenv' -import colors from 'colors' -import { register } from 'ts-node' -import yargs, { CommandModule } from 'yargs' -import { hideBin } from 'yargs/helpers' - -import IArgs from './interfaces/IArgs' -import IConfiguration from './interfaces/IConfig' - -import { registerOptions } from './options' -import Migrator from './migrator' - -dotenv.config() -colors.enable() -register(registerOptions) - -export const getMigrator = async (args: IArgs): Promise => { - if (!args.d && !args.connectionString) { - console.error('You need to provide the Mongo URI to persist migration status.\nUse option --connectionString / -d to provide the URI.'.red) - process.exit(1) - } - - const migrator = new Migrator({ - migrationsPath: path.resolve(args.md), - templatePath: args.t, - connectionString: args.d, - collection: args.collection, - autosync: args.autosync, - cli: true - }) - - await migrator.connection.asPromise() - if (migrator.connection.readyState === 1) { - colors.green('Connected to MongoDB') - } - - return migrator -} - -export const listCmd: CommandModule = { - command: 'list', - aliases: ['ls'], - describe: 'Lists all migrations and their current state.', - handler: async (args: IArgs) => { - const migrator = await getMigrator(args) - await migrator.list() - } -} - -export const createCmd: CommandModule = { - command: 'create ', - aliases: ['touch'], - describe: 'Creates a new migration file.', - builder: (yargs) => yargs.positional('migration-name', { - describe: 'The name of the migration to create', - type: 'string' - }), - handler: async (args: IArgs) => { - const migrator = await getMigrator(args) - await migrator.create(args.migrationName) - console.log('Migration created. Run ' + `mongoose-migrate up ${args.migrationName}`.cyan + ' to apply the migration.') - } -} - -export const upCmd: CommandModule = { - command: 'up [migration-name]', - describe: 'Migrates all the migration files that have not yet been run in chronological order. ' + - 'Not including [migration-name] will run up on all migrations that are in a down state.', - builder: (yargs) => yargs.positional('migration-name', { - describe: 'The name of the migration to create', - type: 'string' - }), - handler: async (args: IArgs) => { - const migrator = await getMigrator(args) - await migrator.run('up', args.migrationName) - } -} - -export const downCmd: CommandModule = { - command: 'down ', - describe: 'Rolls back migrations down to given name (if down function was provided)', - builder: (yargs) => yargs.positional('migration-name', { - describe: 'The name of the migration to create', - type: 'string' - }), - handler: async (args: IArgs) => { - const migrator = await getMigrator(args) - await migrator.run('down', args.migrationName) - } -} - -export const pruneCmd: CommandModule = { - command: 'prune', - describe: 'Allows you to delete extraneous migrations by removing extraneous local migration files/database migrations.', - handler: async (args: IArgs) => { - const migrator = await getMigrator(args) - await migrator.prune() - } -} - -export const cli = async () => { - await yargs(hideBin(process.argv)).usage('Usage: migrate -d [[create|up|down ]|list] [optional options]') - .default('config', 'migrate') - .config('config', 'filepath to an options configuration json file', (pathToConfigFile: string) => { - let options: IConfiguration - - try { - options = require(path.resolve(pathToConfigFile)) - } catch (err) { - options = {} - } - - const config: IConfiguration = { - connectionString: process.env.MIGRATE_CONNECTION_STRING || - process.env.migrateConnectionString || - options.connectionString || - null, - templatePath: process.env.MIGRATE_TEMPLATE_PATH || - process.env.migrateTemplatePath || - options.templatePath || - null, - migrationsPath: process.env.MIGRATE_MIGRATIONS_PATH || - process.env.migrateMigrationsPath || - options.migrationsPath || - 'migrations', - collection: process.env.MIGRATE_COLLECTION || - process.env.migrateCollection || - options.collection || - 'migrations', - autosync: Boolean(process.env.MIGRATE_AUTOSYNC) || - Boolean(process.env.migrateAutosync) || - options.autosync || - false - } - - return { - d: config.connectionString, - t: config.templatePath, - md: config.migrationsPath, - collection: config.collection, - autosync: config.autosync - } - }) - .command(listCmd).example('migrate list'.cyan, 'Lists all migrations and their current state.') - .command(createCmd).example('migrate create add_users'.cyan, 'Creates a new migration file.') - .command(upCmd).example('migrate up add_user'.cyan, 'Runs up on the add_user migration.') - .command(downCmd).example('migrate down delete_names'.cyan, 'Runs down on the delete_names migration.') - .command(pruneCmd).example('migrate prune'.cyan, 'Deletes extraneous migrations.') - .option('collection', { - description: 'The collection to use for the migrations', - type: 'string', - default: 'migrations', - nargs: 1 - }) - .option('d', { - demand: true, - alias: 'connectionString', - type: 'string', - description: 'The URI of the database connection'.yellow, - nargs: 1 - }) - .option('md', { - normalize: true, - alias: 'migrations-dir', - description: 'The path to the migration files', - default: './migrations', - nargs: 1 - }) - .option('t', { - normalize: true, - alias: 'template-file', - description: 'The template file to use when creating a migration', - type: 'string', - nargs: 1 - }) - .option('autosync', { - type: 'boolean', - description: 'Automatically add new migrations in the migrations folder to the database instead of asking interactively' - }) - .option('c', { - normalize: true, - alias: 'change-dir', - description: 'Change current working directory before running anything', - type: 'string', - nargs: 1 - }) - .help('h').alias('h', 'help') - .demandCommand() - .parse() - - process.exit(0) -} diff --git a/src/commander.ts b/src/commander.ts new file mode 100644 index 0000000..b27aa83 --- /dev/null +++ b/src/commander.ts @@ -0,0 +1,142 @@ +import dotenv from 'dotenv' +import path from 'path' +import colors from 'colors' +import { Command, OptionValues } from 'commander' +import Migrator from './migrator' +import { register } from 'ts-node' +import { registerOptions } from './options' +import IOptions from './interfaces/IOptions' + +dotenv.config() +colors.enable() +register(registerOptions) + +export const getMigrator = async (options: IOptions): Promise => { + let fileOptions: IOptions = {} + if (options.configPath) { + try { + const configPath = path.resolve(options.configPath) + fileOptions = (await import(configPath)).default + } catch (err) { + fileOptions = {} + } + } + + const uri = options.uri || + process.env.MIGRATE_MONGO_URI || + process.env.migrateMongoUri || + fileOptions.uri + + const collection = options.collection || + process.env.MIGRATE_MONGO_COLLECTION || + process.env.migrateMongoCollection || + fileOptions.collection + + const migrationsPath = options.migrationsPath || + process.env.MIGRATE_MIGRATIONS_PATH || + process.env.migrateMigrationsPath || + fileOptions.migrationsPath + + const templatePath = options.templatePath || + process.env.MIGRATE_TEMPLATE_PATH || + process.env.migrateTemplatePath || + fileOptions.templatePath + + const autosync = Boolean(options.autosync || + process.env.MIGRATE_AUTOSYNC || + process.env.migrateAutosync || + fileOptions.autosync) + + if (!uri) { + throw new Error('You need to provide the MongoDB Connection URI to persist migration status.\nUse option --uri / -d to provide the URI.'.red) + } + + const migrator = new Migrator({ + migrationsPath, + templatePath, + uri, + collection, + autosync, + cli: true + }) + + await migrator.connected() + + return migrator +} + +export class Migrate { + private program: Command + private migrator: Migrator + constructor (public exit: boolean = true) { + this.exit = exit + this.program = new Command() + this.program + .name('migrate') + .description('CLI migration tool for mongoose'.cyan) + .option('-f, --config-path ', 'path to the config file', 'migrate') + .option('-d, --uri ', 'mongo connection string'.yellow) + .option('-c, --collection ', 'collection name to use for the migrations', 'migrations') + .option('-a, --autosync ', 'automatically sync new migrations without prompt', false) + .option('-m, --migrations-path ', 'path to the migration files', './migrations') + .option('-t, --template-path ', 'template file to use when creating a migration') + .option('-cd, --change-dir ', 'change current working directory before running anything') + .hook('preAction', async () => { + const opts = this.program.opts() + this.migrator = await getMigrator(opts) + }) + + this.program + .command('list') + .description('list all migrations') + .action(async () => { + console.log('Listing migrations'.cyan) + await this.migrator.list() + }) + + this.program + .command('create ') + .description('create a new migration file') + .action(async (migrationName) => { + await this.migrator.create(migrationName) + console.log('Migration created. Run ' + `migrate up ${migrationName}`.cyan + ' to apply the migration') + }) + + this.program + .command('up [migration-name]') + .description('run all migrations or a specific migration if name provided') + .action(async (migrationName) => { + await this.migrator.run('up', migrationName) + }) + + this.program + .command('down ') + .description('roll back migrations down to given name') + .action(async (migrationName) => { + await this.migrator.run('down', migrationName) + }) + + this.program + .command('prune') + .description('delete extraneous migrations from migration folder or database') + .action(async () => { + await this.migrator.prune() + }) + } + + public async run (): Promise { + return this.program.parseAsync(process.argv) + .then(async () => { + await this.migrator.close() + if (this.exit) process.exit(0) + return this.program.opts() + }) + .catch((err) => { + console.error(err.message.red) + if (this.exit) process.exit(1) + throw err + }) + } +} + +export const migrate = new Migrate() diff --git a/src/interfaces/IArgs.ts b/src/interfaces/IArgs.ts deleted file mode 100644 index 471fb40..0000000 --- a/src/interfaces/IArgs.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ArgumentsCamelCase } from 'yargs' - -interface IArgs extends ArgumentsCamelCase { - md: string - t: string - d: string - collection: string - autosync: boolean - migrationName: string -} - -export default IArgs diff --git a/src/interfaces/IFileError.ts b/src/interfaces/IFileError.ts deleted file mode 100644 index 94671c6..0000000 --- a/src/interfaces/IFileError.ts +++ /dev/null @@ -1,6 +0,0 @@ -interface IFileError extends Error { - code?: string - path?: string -} - -export default IFileError diff --git a/src/interfaces/IMigratorOptions.ts b/src/interfaces/IMigratorOptions.ts index 167ce43..e3fa6a0 100644 --- a/src/interfaces/IMigratorOptions.ts +++ b/src/interfaces/IMigratorOptions.ts @@ -3,7 +3,7 @@ import { Connection } from 'mongoose' interface IMigratorOptions { templatePath?: string migrationsPath?: string - connectionString: string + uri: string collection?: string autosync: boolean cli?: boolean diff --git a/src/interfaces/IConfig.ts b/src/interfaces/IOptions.ts similarity index 53% rename from src/interfaces/IConfig.ts rename to src/interfaces/IOptions.ts index 701a072..20a4c7c 100644 --- a/src/interfaces/IConfig.ts +++ b/src/interfaces/IOptions.ts @@ -1,9 +1,10 @@ -interface IConfiguration { - connectionString?: string +interface IOptions { + configPath?: string + uri?: string templatePath?: string migrationsPath?: string collection?: string autosync?: boolean } -export default IConfiguration +export default IOptions diff --git a/src/migrator.ts b/src/migrator.ts index f784c70..1409690 100644 --- a/src/migrator.ts +++ b/src/migrator.ts @@ -7,8 +7,6 @@ import { register } from 'ts-node' import mongoose, { Connection, FilterQuery, HydratedDocument, LeanDocument, Model } from 'mongoose' -import IArgs from './interfaces/IArgs' -import IFileError from './interfaces/IFileError' import IMigration from './interfaces/IMigration' import IMigratorOptions from './interfaces/IMigratorOptions' @@ -45,7 +43,7 @@ class Migrator { constructor (options: IMigratorOptions) { this.template = options.templatePath ? fs.readFileSync(options.templatePath, 'utf-8') : defaultTemplate this.migrationPath = path.resolve(options.migrationsPath || './migrations') - this.connection = options.connection || mongoose.createConnection(options.connectionString, { autoCreate: true }) + this.connection = options.connection || mongoose.createConnection(options.uri, { autoCreate: true }) this.collection = options.collection || 'migrations' this.autosync = options.autosync || false this.cli = options.cli @@ -56,18 +54,16 @@ class Migrator { } } + async connected (): Promise { + return this.connection.asPromise() + } + log (logString: string, force = false) { if (force || this.cli) { console.log(logString) } } - fileError (error: IFileError): void { - if (error && error.code === 'ENOENT') { - throw new ReferenceError(`Could not find any files at path '${error.path}'`) - } - } - /** * Use your own Mongoose connection object (so you can use this('modelname') * @param {mongoose.connection} connection - Mongoose connection @@ -93,28 +89,24 @@ class Migrator { * @returns {Promise} A promise of the Migration created */ async create (migrationName: string): Promise> { - try { - const existingMigration = await this.migrationModel.findOne({ name: migrationName }).exec() - if (existingMigration) { - throw new Error(`There is already a migration with name '${migrationName}' in the database`.red) - } - - await this.sync() - const now = Date.now() - const newMigrationFile = `${now}-${migrationName}.ts` - fs.writeFileSync(path.join(this.migrationPath, newMigrationFile), this.template) - // create instance in db - await this.connection.asPromise() - const migrationCreated = await this.migrationModel.create({ - name: migrationName, - createdAt: now - }) - this.log(`Created migration ${migrationName} in ${this.migrationPath}.`) - return migrationCreated - } catch (error) { - this.log(error.stack) - this.fileError(error) + await this.connected() + const existingMigration = await this.migrationModel.findOne({ name: migrationName }).exec() + if (existingMigration) { + throw new Error(`There is already a migration with name '${migrationName}' in the database`.red) } + + await this.sync() + const now = Date.now() + const newMigrationFile = `${now}-${migrationName}.ts` + fs.writeFileSync(path.join(this.migrationPath, newMigrationFile), this.template) + // create instance in db + await this.connected() + const migrationCreated = await this.migrationModel.create({ + name: migrationName, + createdAt: now + }) + this.log(`Created migration ${migrationName} in ${this.migrationPath}`) + return migrationCreated } /** @@ -122,7 +114,8 @@ class Migrator { * @param migrationName * @param direction */ - async run (direction = 'up', migrationName?: string, ...args: IArgs[]): Promise[]> { + async run (direction = 'up', migrationName?: string, ...args): Promise[]> { + await this.connected() await this.sync() if (direction !== 'up' && direction !== 'down') { @@ -135,7 +128,7 @@ class Migrator { if (!untilMigration) { if (migrationName) throw new ReferenceError('Could not find that migration in the database') - else throw new Error('There are no pending migrations.') + else throw new Error('There are no pending migrations') } let query: FilterQuery = { @@ -189,19 +182,19 @@ class Migrator { } }) - this.log(`${direction.toUpperCase()}: `[direction === 'up' ? 'green' : 'red'] + ` ${migration.filename} `) + this.log(`${direction}:`[direction === 'up' ? 'green' : 'red'] + ` ${migration.filename} `) await this.migrationModel.where({ name: migration.name }).updateMany({ $set: { state: direction } }).exec() migrationsRan.push(migration.toJSON()) numMigrationsRan++ } catch (err) { - this.log(`Failed to run migration ${migration.name} due to an error.`.red) + this.log(`Failed to run migration ${migration.name} due to an error`.red) this.log('Not continuing. Make sure your data is in consistent state'.red) throw err instanceof (Error) ? err : new Error(err) } } - if (migrationsToRun.length === numMigrationsRan && numMigrationsRan > 0) this.log('All migrations finished successfully.'.green) + if (migrationsToRun.length === numMigrationsRan && numMigrationsRan > 0) this.log('All migrations finished successfully'.green) return migrationsRan } @@ -212,6 +205,7 @@ class Migrator { * This functionality is opposite of prune() */ async sync (): Promise[]> { + await this.connected() try { const filesInMigrationFolder = fs.readdirSync(this.migrationPath) const migrationsInDatabase = await this.migrationModel.find({}).exec() @@ -229,7 +223,7 @@ class Migrator { if (!this.autosync && migrationsToImport.length) { const answers: { migrationsToImport: string[] } = await inquirer.prompt({ type: 'checkbox', - message: 'The following migrations exist in the migrations folder but not in the database. Select the ones you want to import into the database', + message: 'The following migrations exist in the migrations folder but not in the database.\nSelect the ones you want to import into the database', name: 'migrationsToImport', choices: filesNotInDb }) @@ -253,7 +247,7 @@ class Migrator { return Promise.all(promises) } catch (error) { - this.log('Could not synchronize migrations in the migrations folder up to the database.'.red) + this.log('Could not synchronize migrations in the migrations folder up to the database'.red) throw error } } @@ -263,6 +257,7 @@ class Migrator { * Removes files in migration directory which don't exist in database. */ async prune () { + await this.connected() try { const filesInMigrationFolder = fs.readdirSync(this.migrationPath) const migrationsInDatabase = await this.migrationModel.find({}).exec() @@ -300,7 +295,7 @@ class Migrator { return migrationsToDeleteDocs } catch (error) { - this.log('Could not prune extraneous migrations from database.'.red) + this.log('Could not prune extraneous migrations from database'.red) throw error } } @@ -313,9 +308,10 @@ class Migrator { * ] */ async list (): Promise[]> { + await this.connected() await this.sync() const migrations = await this.migrationModel.find().sort({ createdAt: 1 }).exec() - if (!migrations.length) this.log('There are no migrations to list.'.yellow) + if (!migrations.length) this.log('There are no migrations to list'.yellow) return migrations.map((migration: HydratedDocument) => { this.log( `${migration.state}: `[migration.state === 'up' ? 'green' : 'red'] + `${migration.filename}` diff --git a/tests/cli.test.ts b/tests/cli.test.ts new file mode 100644 index 0000000..37f0cd2 --- /dev/null +++ b/tests/cli.test.ts @@ -0,0 +1,88 @@ +import colors from 'colors' +import mongoose, { Connection } from 'mongoose' +import { getMigrator, Migrate } from '../src/commander' +import { clearDirectory } from '../utils/filesystem' + +colors.enable() + +const exec = (...args: string[]) => { + const migrate = new Migrate(false) + process.argv = ['node', 'migrate', ...args] + return migrate.run() +} + +describe('cli', () => { + const uri = `${globalThis.__MONGO_URI__}${globalThis.__MONGO_DB_NAME__}` + let connection: Connection + + beforeAll(async () => { + clearDirectory('migrations') + connection = await mongoose.createConnection(uri).asPromise() + }) + + afterAll(async () => { + if (connection.readyState !== 0) { + await connection.close() + } + }) + + it('should get migrator instance', async () => { + const migrator = await getMigrator({ + uri + }) + const connection = await migrator.connected() + expect(migrator).toBeDefined() + expect(connection).toBeDefined() + expect(connection.readyState).toBe(1) + await migrator.close() + expect(connection.readyState).toBe(0) + }) + + it('should run list command', async () => { + const consoleSpy = jest.spyOn(console, 'log') + const opts = await exec('list', '-d', uri) + expect(opts?.configPath).toBe('migrate') + expect(opts?.uri).toBe(uri) + expect(opts?.collection).toBe('migrations') + expect(opts?.autosync).toBe(false) + expect(opts?.migrationsPath).toBe('./migrations') + expect(consoleSpy).toBeCalledWith('Listing migrations'.cyan) + expect(consoleSpy).toBeCalledWith('There are no migrations to list'.yellow) + }) + + it('should run create command', async () => { + const consoleSpy = jest.spyOn(console, 'log') + const opts = await exec('create', 'migration-name-test', '-d', uri) + expect(opts?.configPath).toBe('migrate') + expect(opts?.uri).toBe(uri) + expect(opts?.collection).toBe('migrations') + expect(opts?.autosync).toBe(false) + expect(opts?.migrationsPath).toBe('./migrations') + expect(consoleSpy).toBeCalledWith(expect.stringMatching(/^Created migration migration-name-test in/)) + expect(consoleSpy).toBeCalledWith(expect.stringMatching(/^Migration created/)) + }) + + it('should run up command', async () => { + const consoleSpy = jest.spyOn(console, 'log') + const opts = await exec('up', '-d', uri) + expect(opts?.configPath).toBe('migrate') + expect(opts?.uri).toBe(uri) + expect(opts?.collection).toBe('migrations') + expect(opts?.autosync).toBe(false) + expect(opts?.migrationsPath).toBe('./migrations') + expect(consoleSpy).toBeCalledWith(expect.stringMatching(/^up:/) && expect.stringMatching(/migration-name-test/)) + expect(consoleSpy).toBeCalledWith('All migrations finished successfully'.green) + }) + + it('should run down command', async () => { + const consoleSpy = jest.spyOn(console, 'log') + const opts = await exec('down', 'migration-name-test', '-d', uri) + expect(opts?.configPath).toBe('migrate') + expect(opts?.uri).toBe(uri) + expect(opts?.collection).toBe('migrations') + expect(opts?.autosync).toBe(false) + expect(opts?.migrationsPath).toBe('./migrations') + expect(consoleSpy).toBeCalledWith(expect.stringMatching(/^down:/) && expect.stringMatching(/migration-name-test/)) + expect(consoleSpy).toBeCalledWith('All migrations finished successfully'.green) + }) +}) diff --git a/tests/migrator.test.ts b/tests/migrator.test.ts index b4d63a8..ed4144c 100644 --- a/tests/migrator.test.ts +++ b/tests/migrator.test.ts @@ -3,15 +3,17 @@ import mongoose, { Connection, Types } from 'mongoose' import Migrator from '../src/migrator' import IMigratorOptions from '../src/interfaces/IMigratorOptions' +import { clearDirectory } from '../utils/filesystem' colors.enable() describe('library', () => { - const connectionString = `${globalThis.__MONGO_URI__}${globalThis.__MONGO_DB_NAME__}` + const uri = `${globalThis.__MONGO_URI__}${globalThis.__MONGO_DB_NAME__}` let connection: Connection beforeAll(async () => { - connection = await mongoose.createConnection(connectionString).asPromise() + clearDirectory('migrations') + connection = await mongoose.createConnection(uri).asPromise() }) afterAll(async () => { @@ -22,8 +24,7 @@ describe('library', () => { it('should insert a doc into collection with migrator', async () => { const options: IMigratorOptions = { - connectionString, - // connection, + uri, autosync: true } const migrator = new Migrator(options) diff --git a/tests/mongose.test.ts b/tests/mongose.test.ts index 8877804..0870227 100644 --- a/tests/mongose.test.ts +++ b/tests/mongose.test.ts @@ -1,11 +1,11 @@ import mongoose, { Connection } from 'mongoose' describe('mongoose', () => { - const connectionString = `${globalThis.__MONGO_URI__}${globalThis.__MONGO_DB_NAME__}` + const uri = `${globalThis.__MONGO_URI__}${globalThis.__MONGO_DB_NAME__}` let connection: Connection beforeAll(async () => { - connection = await mongoose.createConnection(connectionString).asPromise() + connection = await mongoose.createConnection(uri).asPromise() }) afterAll(async () => { diff --git a/tools/packagejson.js b/tools/packagejson.js deleted file mode 100644 index 34d9ccb..0000000 --- a/tools/packagejson.js +++ /dev/null @@ -1,26 +0,0 @@ - -/* eslint-disable */ -const fs = require('fs') -const Path = require('path') -const fileName = '../package.json' -const file = require(fileName) -/* eslint-enable */ - -const args = process.argv.slice(2) - -for (let i = 0, l = args.length; i < l; i++) { - if (i % 2 === 0) { - file[args[i]] = args[i + 1] - } -} - -fs.writeFile( - Path.join(__dirname, fileName), - JSON.stringify(file, null, 2), - (err) => { - if (err) { - return console.log(err) - } - console.log('Writing to ' + fileName) - } -) diff --git a/tsconfig.json b/tsconfig.json index 23fce79..f2f86eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,14 @@ { "compilerOptions": { "target": "es2021", - "lib": ["es2021"], + "lib": [ + "es2021" + ], "module": "commonjs", "outDir": "dist/", "moduleResolution": "node", - "declaration": true, - "declarationMap": true, + "declaration": true, + "declarationMap": true, "sourceMap": true, "allowJs": true, "allowSyntheticDefaultImports": true, @@ -15,7 +17,9 @@ "importHelpers": true, "removeComments": true }, - "include": ["src/**/*"], + "include": [ + "src/**/*" + ], "exclude": [ "dist", "tools", diff --git a/utils/filesystem.ts b/utils/filesystem.ts new file mode 100644 index 0000000..d42de9c --- /dev/null +++ b/utils/filesystem.ts @@ -0,0 +1,13 @@ +import fs from 'fs' +import path from 'path' + +export const clearDirectory = (dir: string) => { + if (!fs.existsSync(dir)) return + const files = fs.readdirSync(dir) + for (const file of files) { + const filePath = path.join(dir, file) + if (fs.statSync(filePath).isFile()) { + fs.unlinkSync(filePath) + } + } +}