From 42f98e681af920d119285b06d14fedc3775f5165 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 13:40:58 +0200 Subject: [PATCH 01/10] feat: adds glob support for `dir` and `ignorePattern` Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- bin/node-pg-migrate.ts | 14 ++++- docs/src/api.md | 5 +- docs/src/cli.md | 5 +- package.json | 1 + pnpm-lock.yaml | 3 + src/migration.ts | 76 +++++++++++++++++++---- src/runner.ts | 19 +++--- src/types.ts | 24 ++++++- test/migration.spec.ts | 72 ++++++++++++++++++++- test/migrations/nested/001_nested_noop.js | 1 + 10 files changed, 183 insertions(+), 37 deletions(-) create mode 100644 test/migrations/nested/001_nested_noop.js diff --git a/bin/node-pg-migrate.ts b/bin/node-pg-migrate.ts index 11c243d3..34863bf3 100755 --- a/bin/node-pg-migrate.ts +++ b/bin/node-pg-migrate.ts @@ -48,6 +48,7 @@ const schemaArg = 'schema'; const createSchemaArg = 'create-schema'; const databaseUrlVarArg = 'database-url-var'; const migrationsDirArg = 'migrations-dir'; +const useGlobArg = 'use-glob'; const migrationsTableArg = 'migrations-table'; const migrationsSchemaArg = 'migrations-schema'; const createMigrationsSchemaArg = 'create-migrations-schema'; @@ -84,9 +85,14 @@ const parser = yargs(process.argv.slice(2)) [migrationsDirArg]: { alias: 'm', defaultDescription: '"migrations"', - describe: 'The directory containing your migration files', + describe: `The directory name or glob pattern containing your migration files (resolved from cwd()). When using glob pattern, '${useGlobArg}' must be used as well`, type: 'string', }, + [useGlobArg]: { + defaultDescription: 'false', + describe: `Use glob to find migration files. This will use '${migrationsDirArg}' _and_ '${ignorePatternArg}' to glob-search for migration files.`, + type: 'boolean', + }, [migrationsTableArg]: { alias: 't', defaultDescription: '"pgmigrations"', @@ -128,7 +134,7 @@ const parser = yargs(process.argv.slice(2)) }, [ignorePatternArg]: { defaultDescription: '"\\..*"', - describe: 'Regex pattern for file names to ignore', + describe: `Regex or glob pattern for migration files to be ignored. When using glob pattern, '${useGlobArg}' must be used as well`, type: 'string', }, [decamelizeArg]: { @@ -253,6 +259,7 @@ if (dotenv) { } let MIGRATIONS_DIR = argv[migrationsDirArg]; +let USE_GLOB = argv[useGlobArg]; let DB_CONNECTION: string | ConnectionParameters | ClientConfig | undefined = process.env[argv[databaseUrlVarArg]]; let IGNORE_PATTERN = argv[ignorePatternArg]; @@ -469,11 +476,11 @@ const action = argv._.shift(); // defaults MIGRATIONS_DIR ??= join(cwd(), 'migrations'); +USE_GLOB ??= false; MIGRATIONS_FILE_LANGUAGE ??= 'js'; MIGRATIONS_FILENAME_FORMAT ??= 'timestamp'; MIGRATIONS_TABLE ??= 'pgmigrations'; SCHEMA ??= ['public']; -IGNORE_PATTERN ??= '\\..*'; CHECK_ORDER ??= true; VERBOSE ??= true; @@ -583,6 +590,7 @@ if (action === 'create') { }, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion dir: MIGRATIONS_DIR!, + useGlob: USE_GLOB, ignorePattern: IGNORE_PATTERN, schema: SCHEMA, createSchema: CREATE_SCHEMA, diff --git a/docs/src/api.md b/docs/src/api.md index 90540c4e..dfd24ce5 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -15,12 +15,13 @@ which takes options argument with the following structure (similar to [command l | `migrationsTable` | `string` | The table storing which migrations have been run | | `migrationsSchema` | `string` | The schema storing table which migrations have been run (defaults to same value as `schema`) | | `schema` | `string or array[string]` | The schema on which migration will be run (defaults to `public`) | -| `dir` | `string` | The directory containing your migration files | +| `dir` | `string or array[string]` | The directory containing your migration files. This path is resolved from `cwd()`. Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or an array of glob patterns and set `useGlob = true`. Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | +| `useGlob` | `boolean` | Use [glob](https://www.npmjs.com/package/glob) to find migration files. This will use `dir` _and_ `ignorePattern` to glob-search for migration files. Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | | `checkOrder` | `boolean` | Check order of migrations before running them | | `direction` | `enum` | `up` or `down` | | `count` | `number` | Amount of migration to run | | `timestamp` | `boolean` | Treats `count` as timestamp | -| `ignorePattern` | `string` | Regex pattern for file names to ignore (ignores files starting with `.` by default) | +| `ignorePattern` | `string or array[string]` | Regex pattern for file names to ignore (ignores files starting with `.` by default). Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or an array of glob patterns and set `isGlob = true`.Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | | `file` | `string` | Run-only migration with this name | | `singleTransaction` | `boolean` | Combines all pending migrations into a single transaction so that if any migration fails, all will be rolled back (defaults to `true`) | | `createSchema` | `boolean` | Creates the configured schema if it doesn't exist | diff --git a/docs/src/cli.md b/docs/src/cli.md index 4a264bb9..ea5e25a8 100644 --- a/docs/src/cli.md +++ b/docs/src/cli.md @@ -87,11 +87,12 @@ You can adjust defaults by passing arguments to `node-pg-migrate`: | `schema` | `s` | `public` | The schema(s) on which migration will be run, used to set `search_path` | | `create-schema` | | `false` | Create the configured schema if it doesn't exist | | `database-url-var` | `d` | `DATABASE_URL` | Name of env variable with database url string | -| `migrations-dir` | `m` | `migrations` | The directory containing your migration files | +| `migrations-dir` | `m` | `migrations` | The directory containing your migration files. This path is resolved from `cwd()`. Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern and set `--use-glob`. Note: enabling glob will read both, `--migrations-dir` _and_ `--ignore-pattern` as glob patterns | +| `use-glob` | | `false` | Use [glob](https://www.npmjs.com/package/glob) to find migration files. This will use `--migrations-dir` _and_ `--ignore-pattern` to glob-search for migration files.| | `migrations-schema` | | same value as `schema` | The schema storing table which migrations have been run | | `create-migrations-schema` | | `false` | Create the configured migrations schema if it doesn't exist | | `migrations-table` | `t` | `pgmigrations` | The table storing which migrations have been run | -| `ignore-pattern` | | `undefined` | Regex pattern for file names to ignore | +| `ignore-pattern` | | `undefined` | Regex pattern for file names to ignore (ignores files starting with `.` by default). Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern and set `--use-glob`. Note: enabling glob will read both, `--migrations-dir` _and_ `--ignore-pattern` as glob patterns | | `migration-filename-format` | | `timestamp` | Choose prefix of file, `utc` (`20200605075829074`) or `timestamp` (`1591343909074`) | | `migration-file-language` | `j` | `js` | Language of the migration file to create (`js`, `ts` or `sql`) | | `template-file-name` | | `undefined` | Utilize a custom migration template file with language inferred from its extension. The file should export the up method, accepting a MigrationBuilder instance. | diff --git a/package.json b/package.json index eff94aa6..bed7f44a 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "url": "git+https://github.com/salsita/node-pg-migrate.git" }, "dependencies": { + "glob": "11.0.0", "yargs": "~17.7.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17fa7b25..c206d67c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + glob: + specifier: 11.0.0 + version: 11.0.0 yargs: specifier: ~17.7.0 version: 17.7.2 diff --git a/src/migration.ts b/src/migration.ts index 6900b0f2..ee4e24b8 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -6,6 +6,7 @@ */ +import { glob } from 'glob'; import { createReadStream, createWriteStream } from 'node:fs'; import { mkdir, readdir } from 'node:fs/promises'; import { basename, extname, join, resolve } from 'node:path'; @@ -49,19 +50,67 @@ export type CreateOptions = { const SEPARATOR = '_'; +function compareStringsByValue(a: string, b: string): number { + return a.localeCompare(b, 'en', { + usage: 'sort', + numeric: true, + sensitivity: 'variant', + }); +} + +function compareFileNamesByTimestamp( + a: string, + b: string, + logger?: Logger +): number { + const aTimestamp = getTimestamp(a, logger); + const bTimestamp = getTimestamp(b, logger); + + return aTimestamp - bTimestamp; +} + +// TODO should be renamed to make clear that this function doesn't actually load the files - it only reads their names / paths from `dir` export async function loadMigrationFiles( - dir: string, - ignorePattern?: string + dir: string | string[], + ignorePattern?: string | string[], + useGlob: boolean = false, + logger?: Logger ): Promise<string[]> { + if (useGlob) { + /** + * By default, a `**` in a pattern will follow 1 symbolic link if + * it is not the first item in the pattern, or none if it is the + * first item in the pattern, following the same behavior as Bash. + * + * only want files, no dirs. + */ + const globMatches = await glob(dir, { ignore: ignorePattern, nodir: true }); + return globMatches.sort(compareStringsByValue); + } + + if (Array.isArray(dir) || Array.isArray(ignorePattern)) { + throw new TypeError( + 'Options "dir" and "ignorePattern" can only be arrays when "useGlob" is true' + ); + } + + const ignoreRegexp = new RegExp( + ignorePattern?.length ? `^${ignorePattern}$` : '^\\..*' + ); + const dirContent = await readdir(`${dir}/`, { withFileTypes: true }); - const files = dirContent - .map((file) => (file.isFile() || file.isSymbolicLink() ? file.name : null)) - .filter((file): file is string => Boolean(file)) - .sort(); - const filter = new RegExp(`^(${ignorePattern})$`); - return ignorePattern === undefined - ? files - : files.filter((i) => !filter.test(i)); + return dirContent + .filter( + (dirent) => + (dirent.isFile() || dirent.isSymbolicLink()) && + !ignoreRegexp.test(dirent.name) + ) + .sort( + (a, b) => + compareFileNamesByTimestamp(a.name, b.name, logger) || + compareStringsByValue(a.name, b.name) + ) + .map((dirent) => resolve(dir, dirent.name)); } function getSuffixFromFileName(fileName: string): string { @@ -82,7 +131,10 @@ async function getLastSuffix( } } -export function getTimestamp(logger: Logger, filename: string): number { +export function getTimestamp( + filename: string, + logger: Logger = console +): number { const prefix = filename.split(SEPARATOR)[0]; if (prefix && /^\d+$/.test(prefix)) { if (prefix.length === 13) { @@ -187,7 +239,7 @@ export class Migration implements RunMigration { this.db = db; this.path = migrationPath; this.name = basename(migrationPath, extname(migrationPath)); - this.timestamp = getTimestamp(logger, this.name); + this.timestamp = getTimestamp(this.name, logger); this.up = up; this.down = down; this.options = options; diff --git a/src/runner.ts b/src/runner.ts index 729da38d..a6b382b8 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -32,11 +32,14 @@ async function loadMigrations( ): Promise<Migration[]> { try { let shorthands: ColumnDefinitions = {}; - const files = await loadMigrationFiles(options.dir, options.ignorePattern); + const absoluteFilePaths = await loadMigrationFiles( + options.dir, + options.ignorePattern, + options.useGlob + ); const migrations = await Promise.all( - files.map(async (file) => { - const filePath = resolve(options.dir, file); + absoluteFilePaths.map(async (filePath) => { const actions: MigrationBuilderActions = extname(filePath) === '.sql' ? await migrateSqlFile(filePath) @@ -56,15 +59,7 @@ async function loadMigrations( }) ); - return migrations.sort((m1, m2) => { - const compare = m1.timestamp - m2.timestamp; - - if (compare !== 0) { - return compare; - } - - return m1.name.localeCompare(m2.name); - }); + return migrations; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { throw new Error(`Can't get migration files: ${error.stack}`); diff --git a/src/types.ts b/src/types.ts index d0b59684..3ed4ee52 100644 --- a/src/types.ts +++ b/src/types.ts @@ -783,9 +783,23 @@ export interface RunnerOptionConfig { schema?: string | string[]; /** - * The directory containing your migration files. + * The directory containing your migration files. This path is resolved from `cwd()`. + * Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or + * an array of glob patterns and set `useGlob = true` + * + * Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns */ - dir: string; + dir: string | string[]; + + /** + * Use [glob](https://www.npmjs.com/package/glob) to find migration files. + * This will use `dir` _and_ `ignorePattern` to glob-search for migration files. + * + * Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns + * + * @default: false + */ + useGlob?: boolean; /** * Check order of migrations before running them. @@ -809,8 +823,12 @@ export interface RunnerOptionConfig { /** * Regex pattern for file names to ignore (ignores files starting with `.` by default). + * Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or + * an array of glob patterns and set `isGlob = true` + * + * Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns */ - ignorePattern?: string; + ignorePattern?: string | string[]; /** * Run only migration with this name. diff --git a/test/migration.spec.ts b/test/migration.spec.ts index a2e2ab59..ec484f04 100644 --- a/test/migration.spec.ts +++ b/test/migration.spec.ts @@ -1,7 +1,8 @@ +import { resolve } from 'node:path'; import type { Mock } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { DBConnection } from '../src/db'; -import { getTimestamp, Migration } from '../src/migration'; +import { getTimestamp, loadMigrationFiles, Migration } from '../src/migration'; import type { Logger, RunnerOption } from '../src/types'; const callbackMigration = '1414549381268_names.js'; @@ -33,18 +34,83 @@ describe('migration', () => { it('should get timestamp for normal timestamp', () => { const now = Date.now(); - expect(getTimestamp(logger, String(now))).toBe(now); + expect(getTimestamp(String(now), logger)).toBe(now); }); it('should get timestamp for shortened iso format', () => { const now = new Date(); - expect(getTimestamp(logger, now.toISOString().replace(/\D/g, ''))).toBe( + expect(getTimestamp(now.toISOString().replace(/\D/g, ''), logger)).toBe( now.valueOf() ); }); }); + describe('loadMigrationFiles', () => { + it('should resolve files directly in `dir`', async () => { + const dir = 'test/migrations'; + const resolvedDir = resolve(dir); + const filePaths = await loadMigrationFiles( + dir, + undefined, + undefined, + logger + ); + + expect(Array.isArray(filePaths)).toBeTruthy(); + expect(filePaths).toHaveLength(91); + expect(filePaths).not.toContainEqual(expect.stringContaining('nested')); + + for (const filePath of filePaths) { + expect(filePath).toMatch(resolvedDir); + expect(filePath).toMatch(/\.js$/); + } + }); + + it('should resolve files directly in `dir` and ignore matching ignorePattern', async () => { + const dir = 'test/migrations'; + // ignores those files that have `test` in their name (not in the path, just filename) + const ignorePattern = '.+test.+'; + + const filePaths = await loadMigrationFiles( + dir, + ignorePattern, + undefined, + logger + ); + + expect(Array.isArray(filePaths)).toBeTruthy(); + expect(filePaths).toHaveLength(66); + }); + + it('should resolve files matching `dir` glob (starting from cwd())', async () => { + const dir = 'test/{cockroach,migrations}/**'; + + const filePaths = await loadMigrationFiles(dir, undefined, true, logger); + + expect(Array.isArray(filePaths)).toBeTruthy(); + expect(filePaths).toHaveLength(104); + expect(filePaths).toContainEqual(expect.stringContaining('nested')); + }); + + it('should resolve files matching `dir` glob (starting from cwd()) and ignore matching ignorePattern', async () => { + const dir = 'test/{cockroach,migrations}/**'; + // ignores those files that have `test` in their name (not in the path, just filename) + const ignorePattern = '*/cockroach/*test*'; + + const filePaths = await loadMigrationFiles( + dir, + ignorePattern, + true, + logger + ); + + expect(Array.isArray(filePaths)).toBeTruthy(); + expect(filePaths).toHaveLength(103); + expect(filePaths).toContainEqual(expect.stringContaining('nested')); + }); + }); + describe('self.applyUp', () => { it('should call db.query on normal operations', async () => { const migration = new Migration( diff --git a/test/migrations/nested/001_nested_noop.js b/test/migrations/nested/001_nested_noop.js new file mode 100644 index 00000000..fcb02f0c --- /dev/null +++ b/test/migrations/nested/001_nested_noop.js @@ -0,0 +1 @@ +exports.up = () => {}; From d815fe1846a5b46de948efa1e234c27c1d30a3ee Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 14:10:14 +0200 Subject: [PATCH 02/10] applies proper formatting (prettier + nitpick) Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- bin/node-pg-migrate.ts | 6 ++--- docs/src/api.md | 50 ++++++++++++++++++++-------------------- docs/src/cli.md | 52 +++++++++++++++++++++--------------------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/bin/node-pg-migrate.ts b/bin/node-pg-migrate.ts index 34863bf3..ecb2eb0b 100755 --- a/bin/node-pg-migrate.ts +++ b/bin/node-pg-migrate.ts @@ -85,12 +85,12 @@ const parser = yargs(process.argv.slice(2)) [migrationsDirArg]: { alias: 'm', defaultDescription: '"migrations"', - describe: `The directory name or glob pattern containing your migration files (resolved from cwd()). When using glob pattern, '${useGlobArg}' must be used as well`, + describe: `The directory name or glob pattern containing your migration files (resolved from cwd()). When using glob pattern, "${useGlobArg}" must be used as well`, type: 'string', }, [useGlobArg]: { defaultDescription: 'false', - describe: `Use glob to find migration files. This will use '${migrationsDirArg}' _and_ '${ignorePatternArg}' to glob-search for migration files.`, + describe: `Use glob to find migration files. This will use "${migrationsDirArg}" _and_ "${ignorePatternArg}" to glob-search for migration files.`, type: 'boolean', }, [migrationsTableArg]: { @@ -134,7 +134,7 @@ const parser = yargs(process.argv.slice(2)) }, [ignorePatternArg]: { defaultDescription: '"\\..*"', - describe: `Regex or glob pattern for migration files to be ignored. When using glob pattern, '${useGlobArg}' must be used as well`, + describe: `Regex or glob pattern for migration files to be ignored. When using glob pattern, "${useGlobArg}" must be used as well`, type: 'string', }, [decamelizeArg]: { diff --git a/docs/src/api.md b/docs/src/api.md index dfd24ce5..0e8dc34e 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -8,28 +8,28 @@ which takes options argument with the following structure (similar to [command l > [!NOTE] > If you use `dbClient`, you should not use `databaseUrl` at the same time and vice versa. -| Option | Type | Description | -| ------------------------ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `databaseUrl` | `string or object` | Connection string or client config which is passed to [new pg.Client](https://node-postgres.com/api/client#constructor) | -| `dbClient` | `pg.Client` | Instance of [new pg.Client](https://node-postgres.com/api/client). Instance should be connected to DB, and after finishing migration, user is responsible to close connection | -| `migrationsTable` | `string` | The table storing which migrations have been run | -| `migrationsSchema` | `string` | The schema storing table which migrations have been run (defaults to same value as `schema`) | -| `schema` | `string or array[string]` | The schema on which migration will be run (defaults to `public`) | -| `dir` | `string or array[string]` | The directory containing your migration files. This path is resolved from `cwd()`. Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or an array of glob patterns and set `useGlob = true`. Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | -| `useGlob` | `boolean` | Use [glob](https://www.npmjs.com/package/glob) to find migration files. This will use `dir` _and_ `ignorePattern` to glob-search for migration files. Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | -| `checkOrder` | `boolean` | Check order of migrations before running them | -| `direction` | `enum` | `up` or `down` | -| `count` | `number` | Amount of migration to run | -| `timestamp` | `boolean` | Treats `count` as timestamp | -| `ignorePattern` | `string or array[string]` | Regex pattern for file names to ignore (ignores files starting with `.` by default). Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or an array of glob patterns and set `isGlob = true`.Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | -| `file` | `string` | Run-only migration with this name | -| `singleTransaction` | `boolean` | Combines all pending migrations into a single transaction so that if any migration fails, all will be rolled back (defaults to `true`) | -| `createSchema` | `boolean` | Creates the configured schema if it doesn't exist | -| `createMigrationsSchema` | `boolean` | Creates the configured migration schema if it doesn't exist | -| `noLock` | `boolean` | Disables locking mechanism and checks | -| `fake` | `boolean` | Mark migrations as run without actually performing them (use with caution!) | -| `dryRun` | `boolean` | | -| `log` | `function` | Redirect log messages to this function, rather than `console` | -| `logger` | `object with debug/info/warn/error methods` | Redirect messages to this logger object, rather than `console` | -| `verbose` | `boolean` | Print all debug messages like DB queries run (if you switch it on, it will disable `logger.debug` method) | -| `decamelize` | `boolean` | Runs [`decamelize`](https://github.com/salsita/node-pg-migrate/blob/main/src/utils/decamelize.ts) on table/column/etc. names | +| Option | Type | Description | +| ------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `databaseUrl` | `string or object` | Connection string or client config which is passed to [new pg.Client](https://node-postgres.com/api/client#constructor) | +| `dbClient` | `pg.Client` | Instance of [new pg.Client](https://node-postgres.com/api/client). Instance should be connected to DB, and after finishing migration, user is responsible to close connection | +| `migrationsTable` | `string` | The table storing which migrations have been run | +| `migrationsSchema` | `string` | The schema storing table which migrations have been run (defaults to same value as `schema`) | +| `schema` | `string or array[string]` | The schema on which migration will be run (defaults to `public`) | +| `dir` | `string or array[string]` | The directory containing your migration files. This path is resolved from `cwd()`. Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or an array of glob patterns and set `useGlob = true`. Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | +| `useGlob` | `boolean` | Use [glob](https://www.npmjs.com/package/glob) to find migration files. This will use `dir` _and_ `ignorePattern` to glob-search for migration files. Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | +| `checkOrder` | `boolean` | Check order of migrations before running them | +| `direction` | `enum` | `up` or `down` | +| `count` | `number` | Amount of migration to run | +| `timestamp` | `boolean` | Treats `count` as timestamp | +| `ignorePattern` | `string or array[string]` | Regex pattern for file names to ignore (ignores files starting with `.` by default). Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or an array of glob patterns and set `isGlob = true`. Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns | +| `file` | `string` | Run-only migration with this name | +| `singleTransaction` | `boolean` | Combines all pending migrations into a single transaction so that if any migration fails, all will be rolled back (defaults to `true`) | +| `createSchema` | `boolean` | Creates the configured schema if it doesn't exist | +| `createMigrationsSchema` | `boolean` | Creates the configured migration schema if it doesn't exist | +| `noLock` | `boolean` | Disables locking mechanism and checks | +| `fake` | `boolean` | Mark migrations as run without actually performing them (use with caution!) | +| `dryRun` | `boolean` | | +| `log` | `function` | Redirect log messages to this function, rather than `console` | +| `logger` | `object with debug/info/warn/error methods` | Redirect messages to this logger object, rather than `console` | +| `verbose` | `boolean` | Print all debug messages like DB queries run (if you switch it on, it will disable `logger.debug` method) | +| `decamelize` | `boolean` | Runs [`decamelize`](https://github.com/salsita/node-pg-migrate/blob/main/src/utils/decamelize.ts) on table/column/etc. names | diff --git a/docs/src/cli.md b/docs/src/cli.md index ea5e25a8..da506abd 100644 --- a/docs/src/cli.md +++ b/docs/src/cli.md @@ -80,32 +80,32 @@ More on that below. You can adjust defaults by passing arguments to `node-pg-migrate`: -| Argument | Aliases | Default | Description | -| --------------------------- | ------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `config-file` | `f` | `undefined` | The file with migration JSON config | -| `config-value` | | `db` | Name of config section with db options | -| `schema` | `s` | `public` | The schema(s) on which migration will be run, used to set `search_path` | -| `create-schema` | | `false` | Create the configured schema if it doesn't exist | -| `database-url-var` | `d` | `DATABASE_URL` | Name of env variable with database url string | -| `migrations-dir` | `m` | `migrations` | The directory containing your migration files. This path is resolved from `cwd()`. Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern and set `--use-glob`. Note: enabling glob will read both, `--migrations-dir` _and_ `--ignore-pattern` as glob patterns | -| `use-glob` | | `false` | Use [glob](https://www.npmjs.com/package/glob) to find migration files. This will use `--migrations-dir` _and_ `--ignore-pattern` to glob-search for migration files.| -| `migrations-schema` | | same value as `schema` | The schema storing table which migrations have been run | -| `create-migrations-schema` | | `false` | Create the configured migrations schema if it doesn't exist | -| `migrations-table` | `t` | `pgmigrations` | The table storing which migrations have been run | -| `ignore-pattern` | | `undefined` | Regex pattern for file names to ignore (ignores files starting with `.` by default). Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern and set `--use-glob`. Note: enabling glob will read both, `--migrations-dir` _and_ `--ignore-pattern` as glob patterns | -| `migration-filename-format` | | `timestamp` | Choose prefix of file, `utc` (`20200605075829074`) or `timestamp` (`1591343909074`) | -| `migration-file-language` | `j` | `js` | Language of the migration file to create (`js`, `ts` or `sql`) | -| `template-file-name` | | `undefined` | Utilize a custom migration template file with language inferred from its extension. The file should export the up method, accepting a MigrationBuilder instance. | -| `tsconfig` | | `undefined` | Path to tsconfig.json. Used to setup transpiling of TS migration files. (Also sets `migration-file-language` to typescript, if not overridden) | -| `envPath` | | `same level where it's invoked` | Retrieve the path to a .env file. This feature proves handy when dealing with nested projects or when referencing a global .env file. | -| `timestamp` | | `false` | Treats number argument to up/down migration as timestamp (running up migrations less or equal to timestamp or down migrations greater or equal to timestamp) | -| `check-order` | | `true` | Check order of migrations before running them, to switch it off supply `--no-check-order` | -| `single-transaction` | | `true` | Combines all pending migrations into a single transaction so that if any migration fails, all will be rolled back, to switch it off supply `--no-single-transaction` | -| `no-lock` | | `false` | Disables locking mechanism and checks | -| `fake` | | `false` | Mark migrations as run without actually performing them, (use with caution!) | -| `decamelize` | | `false` | Runs `decamelize` on table/column/etc. names | -| `verbose` | | `true` | Print all debug messages like DB queries run, to switch it off supply `--no-verbose` | -| `reject-unauthorized` | | `undefined` | Sets ssl `rejectUnauthorized` parameter. Use for e.g. self-signed certificates on the server. [see](https://node-postgres.com/announcements#2020-02-25) | +| Argument | Aliases | Default | Description | +| --------------------------- | ------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `config-file` | `f` | `undefined` | The file with migration JSON config | +| `config-value` | | `db` | Name of config section with db options | +| `schema` | `s` | `public` | The schema(s) on which migration will be run, used to set `search_path` | +| `create-schema` | | `false` | Create the configured schema if it doesn't exist | +| `database-url-var` | `d` | `DATABASE_URL` | Name of env variable with database url string | +| `migrations-dir` | `m` | `migrations` | The directory containing your migration files. This path is resolved from `cwd()`. Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern and set `--use-glob`. Note: enabling glob will read both, `--migrations-dir` _and_ `--ignore-pattern` as glob patterns | +| `use-glob` | | `false` | Use [glob](https://www.npmjs.com/package/glob) to find migration files. This will use `--migrations-dir` _and_ `--ignore-pattern` to glob-search for migration files. | +| `migrations-schema` | | same value as `schema` | The schema storing table which migrations have been run | +| `create-migrations-schema` | | `false` | Create the configured migrations schema if it doesn't exist | +| `migrations-table` | `t` | `pgmigrations` | The table storing which migrations have been run | +| `ignore-pattern` | | `undefined` | Regex pattern for file names to ignore (ignores files starting with `.` by default). Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern and set `--use-glob`. Note: enabling glob will read both, `--migrations-dir` _and_ `--ignore-pattern` as glob patterns | +| `migration-filename-format` | | `timestamp` | Choose prefix of file, `utc` (`20200605075829074`) or `timestamp` (`1591343909074`) | +| `migration-file-language` | `j` | `js` | Language of the migration file to create (`js`, `ts` or `sql`) | +| `template-file-name` | | `undefined` | Utilize a custom migration template file with language inferred from its extension. The file should export the up method, accepting a MigrationBuilder instance. | +| `tsconfig` | | `undefined` | Path to tsconfig.json. Used to setup transpiling of TS migration files. (Also sets `migration-file-language` to typescript, if not overridden) | +| `envPath` | | `same level where it's invoked` | Retrieve the path to a .env file. This feature proves handy when dealing with nested projects or when referencing a global .env file. | +| `timestamp` | | `false` | Treats number argument to up/down migration as timestamp (running up migrations less or equal to timestamp or down migrations greater or equal to timestamp) | +| `check-order` | | `true` | Check order of migrations before running them, to switch it off supply `--no-check-order` | +| `single-transaction` | | `true` | Combines all pending migrations into a single transaction so that if any migration fails, all will be rolled back, to switch it off supply `--no-single-transaction` | +| `no-lock` | | `false` | Disables locking mechanism and checks | +| `fake` | | `false` | Mark migrations as run without actually performing them, (use with caution!) | +| `decamelize` | | `false` | Runs `decamelize` on table/column/etc. names | +| `verbose` | | `true` | Print all debug messages like DB queries run, to switch it off supply `--no-verbose` | +| `reject-unauthorized` | | `undefined` | Sets ssl `rejectUnauthorized` parameter. Use for e.g. self-signed certificates on the server. [see](https://node-postgres.com/announcements#2020-02-25) | For SSL connection to DB you can set `PGSSLMODE` environment variable to value from [list](https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT-SSLMODE) other From 3adc21a14b8dd59e4314b7655f584f58a0088808 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 14:17:04 +0200 Subject: [PATCH 03/10] fixes tsup-node warnings about default package exports Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index bed7f44a..17f71245 100644 --- a/package.json +++ b/package.json @@ -34,21 +34,21 @@ "types": "dist/index.d.ts", "exports": { "./dist/*": { + "default": "./dist/*.js", "types": "./dist/*.d.ts", "require": "./dist/*.js", - "import": "./dist/esm/*.mjs", - "default": "./dist/*.js" + "import": "./dist/esm/*.mjs" }, "./bin/*": { + "default": "./bin/*.js", "require": "./bin/*.js", - "import": "./bin/*.mjs", - "default": "./bin/*.js" + "import": "./bin/*.mjs" }, ".": { + "default": "./dist/index.js", "types": "./dist/index.d.ts", "require": "./dist/index.js", - "import": "./dist/esm/index.mjs", - "default": "./dist/index.js" + "import": "./dist/esm/index.mjs" }, "./*": { "types": "./dist/*.d.ts", From ef3e1841fa95112881dd293d209381aa7fae2105 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 15:07:56 +0200 Subject: [PATCH 04/10] ignores punctuation in localeCompare because the previous implementation used that only on the migration file's basename (which also caused two files with same name but different extensions to be treated as equal priority) Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- src/migration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/migration.ts b/src/migration.ts index ee4e24b8..63a8047e 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -51,10 +51,11 @@ export type CreateOptions = { const SEPARATOR = '_'; function compareStringsByValue(a: string, b: string): number { - return a.localeCompare(b, 'en', { + return a.localeCompare(b, undefined, { usage: 'sort', numeric: true, sensitivity: 'variant', + ignorePunctuation: true, }); } From 068c32540d2ed21fa27dd5b11589f42c6eab3c60 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 15:09:44 +0200 Subject: [PATCH 05/10] Revert "fixes tsup-node warnings about default package exports" This reverts commit 3adc21a14b8dd59e4314b7655f584f58a0088808. --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 17f71245..bed7f44a 100644 --- a/package.json +++ b/package.json @@ -34,21 +34,21 @@ "types": "dist/index.d.ts", "exports": { "./dist/*": { - "default": "./dist/*.js", "types": "./dist/*.d.ts", "require": "./dist/*.js", - "import": "./dist/esm/*.mjs" + "import": "./dist/esm/*.mjs", + "default": "./dist/*.js" }, "./bin/*": { - "default": "./bin/*.js", "require": "./bin/*.js", - "import": "./bin/*.mjs" + "import": "./bin/*.mjs", + "default": "./bin/*.js" }, ".": { - "default": "./dist/index.js", "types": "./dist/index.d.ts", "require": "./dist/index.js", - "import": "./dist/esm/index.mjs" + "import": "./dist/esm/index.mjs", + "default": "./dist/index.js" }, "./*": { "types": "./dist/*.d.ts", From 31532aeaeacbef8b62f2d0a49395edaa1d738179 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 15:18:34 +0200 Subject: [PATCH 06/10] cosmetics Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- src/migration.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/migration.ts b/src/migration.ts index 63a8047e..199d47a4 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -50,7 +50,7 @@ export type CreateOptions = { const SEPARATOR = '_'; -function compareStringsByValue(a: string, b: string): number { +function localeCompareStringsNumerically(a: string, b: string): number { return a.localeCompare(b, undefined, { usage: 'sort', numeric: true, @@ -70,7 +70,10 @@ function compareFileNamesByTimestamp( return aTimestamp - bTimestamp; } -// TODO should be renamed to make clear that this function doesn't actually load the files - it only reads their names / paths from `dir` +/** + * ! this function does not actually load the files - it only reads their names / paths from `dir` + * ? should the function body be adjusted to do only what can be expected from it's name + */ export async function loadMigrationFiles( dir: string | string[], ignorePattern?: string | string[], @@ -86,7 +89,7 @@ export async function loadMigrationFiles( * only want files, no dirs. */ const globMatches = await glob(dir, { ignore: ignorePattern, nodir: true }); - return globMatches.sort(compareStringsByValue); + return globMatches.sort(localeCompareStringsNumerically); } if (Array.isArray(dir) || Array.isArray(ignorePattern)) { @@ -109,7 +112,7 @@ export async function loadMigrationFiles( .sort( (a, b) => compareFileNamesByTimestamp(a.name, b.name, logger) || - compareStringsByValue(a.name, b.name) + localeCompareStringsNumerically(a.name, b.name) ) .map((dirent) => resolve(dir, dirent.name)); } @@ -132,6 +135,10 @@ async function getLastSuffix( } } +/** + * ! this function does not only resolve a timestamp numeric value. When prefix is numeric but neither 13 nor 17 digits, it returns the numeric prefix. + * ? should the function body be adjusted to do only what can be expected from it's name + */ export function getTimestamp( filename: string, logger: Logger = console From 09b85c608b5856909aafbe4235d1fe7ad34d1a09 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 19:29:36 +0200 Subject: [PATCH 07/10] removes obsolete colon from jsdoc Co-authored-by: Shinigami <chrissi92@hotmail.de> --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 3ed4ee52..58b73aa4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -797,7 +797,7 @@ export interface RunnerOptionConfig { * * Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns * - * @default: false + * @default false */ useGlob?: boolean; From a64c3bb118180d5350d6c924d2be10fc2ac4d1f9 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Wed, 18 Sep 2024 20:09:54 +0200 Subject: [PATCH 08/10] uses options argument in loadMigrationFiles Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- src/migration.ts | 36 ++++++++++++++++++++++++++++++++---- src/runner.ts | 10 +++++----- test/migration.spec.ts | 29 ++++++++++++----------------- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/migration.ts b/src/migration.ts index 199d47a4..b512c787 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -70,16 +70,44 @@ function compareFileNamesByTimestamp( return aTimestamp - bTimestamp; } +interface LoadMigrationFilesOptions { + /** + * Regex pattern for file names to ignore (ignores files starting with `.` by default). + * Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or + * an array of glob patterns and set `isGlob = true` + * + * Note: enabling glob will read both, `dir` _and_ `ignorePattern` as glob patterns + */ + ignorePattern?: string | string[]; + /** + * Use [glob](https://www.npmjs.com/package/glob) to find migration files. + * This will use `dir` _and_ `options.ignorePattern` to glob-search for migration files. + * + * @default: false + */ + useGlob?: boolean; + /** + * Redirect messages to this logger object, rather than `console`. + */ + logger?: Logger; +} + /** * ! this function does not actually load the files - it only reads their names / paths from `dir` * ? should the function body be adjusted to do only what can be expected from it's name */ export async function loadMigrationFiles( + /** + * The directory containing your migration files. This path is resolved from `cwd()`. + * Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or + * an array of glob patterns and set `options.useGlob = true` + * + * Note: enabling glob will read both, `dir` _and_ `options.ignorePattern` as glob patterns + */ dir: string | string[], - ignorePattern?: string | string[], - useGlob: boolean = false, - logger?: Logger + options: LoadMigrationFilesOptions = {} ): Promise<string[]> { + const { ignorePattern, useGlob = false, logger } = options; if (useGlob) { /** * By default, a `**` in a pattern will follow 1 symbolic link if @@ -126,7 +154,7 @@ async function getLastSuffix( ignorePattern?: string ): Promise<string | undefined> { try { - const files = await loadMigrationFiles(dir, ignorePattern); + const files = await loadMigrationFiles(dir, { ignorePattern }); return files.length > 0 ? getSuffixFromFileName(files[files.length - 1]) : undefined; diff --git a/src/runner.ts b/src/runner.ts index a6b382b8..90a56097 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -32,11 +32,11 @@ async function loadMigrations( ): Promise<Migration[]> { try { let shorthands: ColumnDefinitions = {}; - const absoluteFilePaths = await loadMigrationFiles( - options.dir, - options.ignorePattern, - options.useGlob - ); + const absoluteFilePaths = await loadMigrationFiles(options.dir, { + ignorePattern: options.ignorePattern, + useGlob: options.useGlob, + logger, + }); const migrations = await Promise.all( absoluteFilePaths.map(async (filePath) => { diff --git a/test/migration.spec.ts b/test/migration.spec.ts index ec484f04..8dec7bac 100644 --- a/test/migration.spec.ts +++ b/test/migration.spec.ts @@ -50,12 +50,7 @@ describe('migration', () => { it('should resolve files directly in `dir`', async () => { const dir = 'test/migrations'; const resolvedDir = resolve(dir); - const filePaths = await loadMigrationFiles( - dir, - undefined, - undefined, - logger - ); + const filePaths = await loadMigrationFiles(dir, { logger }); expect(Array.isArray(filePaths)).toBeTruthy(); expect(filePaths).toHaveLength(91); @@ -72,12 +67,10 @@ describe('migration', () => { // ignores those files that have `test` in their name (not in the path, just filename) const ignorePattern = '.+test.+'; - const filePaths = await loadMigrationFiles( - dir, + const filePaths = await loadMigrationFiles(dir, { ignorePattern, - undefined, - logger - ); + logger, + }); expect(Array.isArray(filePaths)).toBeTruthy(); expect(filePaths).toHaveLength(66); @@ -86,7 +79,10 @@ describe('migration', () => { it('should resolve files matching `dir` glob (starting from cwd())', async () => { const dir = 'test/{cockroach,migrations}/**'; - const filePaths = await loadMigrationFiles(dir, undefined, true, logger); + const filePaths = await loadMigrationFiles(dir, { + useGlob: true, + logger, + }); expect(Array.isArray(filePaths)).toBeTruthy(); expect(filePaths).toHaveLength(104); @@ -98,12 +94,11 @@ describe('migration', () => { // ignores those files that have `test` in their name (not in the path, just filename) const ignorePattern = '*/cockroach/*test*'; - const filePaths = await loadMigrationFiles( - dir, + const filePaths = await loadMigrationFiles(dir, { ignorePattern, - true, - logger - ); + useGlob: true, + logger, + }); expect(Array.isArray(filePaths)).toBeTruthy(); expect(filePaths).toHaveLength(103); From a392f31bfd5e5963f5e6a3297a95f44b3b5c6cb0 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Mon, 23 Sep 2024 00:27:48 +0200 Subject: [PATCH 09/10] updates internal function names to match what they do also updates code documentation Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- src/migration.ts | 32 ++++++++++++++++++++++---------- src/runner.ts | 4 ++-- test/migration.spec.ts | 26 +++++++++++++++----------- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/migration.ts b/src/migration.ts index b512c787..fda6f595 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -64,8 +64,8 @@ function compareFileNamesByTimestamp( b: string, logger?: Logger ): number { - const aTimestamp = getTimestamp(a, logger); - const bTimestamp = getTimestamp(b, logger); + const aTimestamp = getNumericPrefix(a, logger); + const bTimestamp = getNumericPrefix(b, logger); return aTimestamp - bTimestamp; } @@ -93,10 +93,19 @@ interface LoadMigrationFilesOptions { } /** - * ! this function does not actually load the files - it only reads their names / paths from `dir` - * ? should the function body be adjusted to do only what can be expected from it's name + * reads files from `dir`, sorts them and returns an array of their absolute paths. + * When not using globs, files are sorted by their numeric prefix values first. 17 digit numbers are interpreted as utc date and converted to the number representation of that date. + * Glob matches are sorted via String.localeCompare with ignored punctuation. + * + * @param dir The directory containing your migration files. This path is resolved from `cwd()`. + * Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or + * an array of glob patterns and set `options.useGlob = true` + * + * Note: enabling glob will read both, `dir` _and_ `options.ignorePattern` as glob patterns + * @param {LoadMigrationFilesOptions} options + * @returns {Promise<Array<String>>} Array of absolute paths */ -export async function loadMigrationFiles( +export async function getMigrationFilePaths( /** * The directory containing your migration files. This path is resolved from `cwd()`. * Alternatively, provide a [glob](https://www.npmjs.com/package/glob) pattern or @@ -154,7 +163,7 @@ async function getLastSuffix( ignorePattern?: string ): Promise<string | undefined> { try { - const files = await loadMigrationFiles(dir, { ignorePattern }); + const files = await getMigrationFilePaths(dir, { ignorePattern }); return files.length > 0 ? getSuffixFromFileName(files[files.length - 1]) : undefined; @@ -164,10 +173,13 @@ async function getLastSuffix( } /** - * ! this function does not only resolve a timestamp numeric value. When prefix is numeric but neither 13 nor 17 digits, it returns the numeric prefix. - * ? should the function body be adjusted to do only what can be expected from it's name + * extracts numeric value from everything in `filename` before `SEPARATOR`. + * 17 digit numbers are interpreted as utc date and converted to the number representation of that date. + * @param filename filename to extract the prefix from + * @param logger Redirect messages to this logger object, rather than `console`. + * @returns {number} numeric value of the filename prefix (everything before SEPARATOR). */ -export function getTimestamp( +export function getNumericPrefix( filename: string, logger: Logger = console ): number { @@ -275,7 +287,7 @@ export class Migration implements RunMigration { this.db = db; this.path = migrationPath; this.name = basename(migrationPath, extname(migrationPath)); - this.timestamp = getTimestamp(this.name, logger); + this.timestamp = getNumericPrefix(this.name, logger); this.up = up; this.down = down; this.options = options; diff --git a/src/runner.ts b/src/runner.ts index 90a56097..97e59211 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -3,7 +3,7 @@ import { extname, resolve } from 'node:path'; import type { DBConnection } from './db'; import Db from './db'; import type { RunMigration } from './migration'; -import { loadMigrationFiles, Migration } from './migration'; +import { getMigrationFilePaths, Migration } from './migration'; import type { ColumnDefinitions } from './operations/tables'; import migrateSqlFile from './sqlMigration'; import type { @@ -32,7 +32,7 @@ async function loadMigrations( ): Promise<Migration[]> { try { let shorthands: ColumnDefinitions = {}; - const absoluteFilePaths = await loadMigrationFiles(options.dir, { + const absoluteFilePaths = await getMigrationFilePaths(options.dir, { ignorePattern: options.ignorePattern, useGlob: options.useGlob, logger, diff --git a/test/migration.spec.ts b/test/migration.spec.ts index 8dec7bac..d22e8747 100644 --- a/test/migration.spec.ts +++ b/test/migration.spec.ts @@ -2,7 +2,11 @@ import { resolve } from 'node:path'; import type { Mock } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { DBConnection } from '../src/db'; -import { getTimestamp, loadMigrationFiles, Migration } from '../src/migration'; +import { + getMigrationFilePaths, + getNumericPrefix, + Migration, +} from '../src/migration'; import type { Logger, RunnerOption } from '../src/types'; const callbackMigration = '1414549381268_names.js'; @@ -30,27 +34,27 @@ describe('migration', () => { dbMock.query = queryMock; }); - describe('getTimestamp', () => { + describe('getMigrationFilePaths', () => { it('should get timestamp for normal timestamp', () => { const now = Date.now(); - expect(getTimestamp(String(now), logger)).toBe(now); + expect(getNumericPrefix(String(now), logger)).toBe(now); }); it('should get timestamp for shortened iso format', () => { const now = new Date(); - expect(getTimestamp(now.toISOString().replace(/\D/g, ''), logger)).toBe( - now.valueOf() - ); + expect( + getNumericPrefix(now.toISOString().replace(/\D/g, ''), logger) + ).toBe(now.valueOf()); }); }); - describe('loadMigrationFiles', () => { + describe('getMigrationFilePaths', () => { it('should resolve files directly in `dir`', async () => { const dir = 'test/migrations'; const resolvedDir = resolve(dir); - const filePaths = await loadMigrationFiles(dir, { logger }); + const filePaths = await getMigrationFilePaths(dir, { logger }); expect(Array.isArray(filePaths)).toBeTruthy(); expect(filePaths).toHaveLength(91); @@ -67,7 +71,7 @@ describe('migration', () => { // ignores those files that have `test` in their name (not in the path, just filename) const ignorePattern = '.+test.+'; - const filePaths = await loadMigrationFiles(dir, { + const filePaths = await getMigrationFilePaths(dir, { ignorePattern, logger, }); @@ -79,7 +83,7 @@ describe('migration', () => { it('should resolve files matching `dir` glob (starting from cwd())', async () => { const dir = 'test/{cockroach,migrations}/**'; - const filePaths = await loadMigrationFiles(dir, { + const filePaths = await getMigrationFilePaths(dir, { useGlob: true, logger, }); @@ -94,7 +98,7 @@ describe('migration', () => { // ignores those files that have `test` in their name (not in the path, just filename) const ignorePattern = '*/cockroach/*test*'; - const filePaths = await loadMigrationFiles(dir, { + const filePaths = await getMigrationFilePaths(dir, { ignorePattern, useGlob: true, logger, From 77720e0d5a141bd2efa858d7a95f5e720a742908 Mon Sep 17 00:00:00 2001 From: Benjamin Kroeger <benjamin.kroeger@gmail.com> Date: Mon, 23 Sep 2024 22:34:02 +0200 Subject: [PATCH 10/10] updates jsdoc comments Signed-off-by: Benjamin Kroeger <benjamin.kroeger@gmail.com> --- src/migration.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/migration.ts b/src/migration.ts index fda6f595..cd0dcc9f 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -93,7 +93,7 @@ interface LoadMigrationFilesOptions { } /** - * reads files from `dir`, sorts them and returns an array of their absolute paths. + * Reads files from `dir`, sorts them and returns an array of their absolute paths. * When not using globs, files are sorted by their numeric prefix values first. 17 digit numbers are interpreted as utc date and converted to the number representation of that date. * Glob matches are sorted via String.localeCompare with ignored punctuation. * @@ -102,8 +102,8 @@ interface LoadMigrationFilesOptions { * an array of glob patterns and set `options.useGlob = true` * * Note: enabling glob will read both, `dir` _and_ `options.ignorePattern` as glob patterns - * @param {LoadMigrationFilesOptions} options - * @returns {Promise<Array<String>>} Array of absolute paths + * @param options + * @returns Array of absolute paths */ export async function getMigrationFilePaths( /** @@ -123,7 +123,7 @@ export async function getMigrationFilePaths( * it is not the first item in the pattern, or none if it is the * first item in the pattern, following the same behavior as Bash. * - * only want files, no dirs. + * Only want files, no dirs. */ const globMatches = await glob(dir, { ignore: ignorePattern, nodir: true }); return globMatches.sort(localeCompareStringsNumerically); @@ -177,7 +177,7 @@ async function getLastSuffix( * 17 digit numbers are interpreted as utc date and converted to the number representation of that date. * @param filename filename to extract the prefix from * @param logger Redirect messages to this logger object, rather than `console`. - * @returns {number} numeric value of the filename prefix (everything before SEPARATOR). + * @returns numeric value of the filename prefix (everything before `SEPARATOR`). */ export function getNumericPrefix( filename: string,