Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions bin/node-pg-migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ const parser = yargs(process.argv.slice(2))
},
[migrationFilenameFormatArg]: {
defaultDescription: '"timestamp"',
choices: ['timestamp', 'utc'],
choices: ['timestamp', 'utc', 'index'],
describe:
'Prefix type of migration filename (Only valid with the create action)',
type: 'string',
Expand Down Expand Up @@ -425,7 +425,8 @@ function readJson(json: unknown): void {
MIGRATIONS_FILENAME_FORMAT,
migrationFilenameFormatArg,
json,
(val): val is FilenameFormat => val === 'timestamp' || val === 'utc'
(val): val is FilenameFormat =>
val === 'timestamp' || val === 'utc' || val === 'index'
);
TEMPLATE_FILE_NAME = applyIf(
TEMPLATE_FILE_NAME,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ You can adjust defaults by passing arguments to `node-pg-migrate`:
| `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-filename-format` | | `timestamp` | Choose prefix of file, `utc` (`20200605075829074`), `timestamp` (`1591343909074`), or `index` (`0012`) |
| `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) |
Expand Down
80 changes: 68 additions & 12 deletions src/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface RunMigration {
export const FilenameFormat = Object.freeze({
timestamp: 'timestamp',
utc: 'utc',
index: 'index',
});

export type FilenameFormat =
Expand All @@ -49,6 +50,7 @@ export interface CreateOptionsDefault {

export type CreateOptions = {
filenameFormat?: FilenameFormat;
ignorePattern?: string;
} & (CreateOptionsTemplate | CreateOptionsDefault);

const SEPARATOR = '_';
Expand Down Expand Up @@ -187,8 +189,11 @@ async function getLastSuffix(
}

/**
* 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.
* 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. 1...4 digit numbers are interpreted as index
* based naming scheme.
*
* @param filename filename to extract the prefix from
* @param logger Redirect messages to this logger object, rather than `console`.
* @returns numeric value of the filename prefix (everything before `SEPARATOR`).
Expand Down Expand Up @@ -219,7 +224,11 @@ export function getNumericPrefix(
}
}

logger.error(`Can't determine timestamp for ${prefix}`);
if (prefix && /^\d{1,4$/) {
Comment thread
Shinigami92 marked this conversation as resolved.
Outdated
return Number(prefix);
}

logger.error(`Cannot determine numeric prefix for ${prefix}`);
return Number(prefix) || 0;
}

Expand All @@ -232,22 +241,69 @@ async function resolveSuffix(
}

export class Migration implements RunMigration {
// class method that creates a new migration file by cloning the migration template
/**
* Get file prefix for a new migrations file
*
* @method Migration.getFilePrefix
* @param filenameFormat Filename format
* @param directory Migrations directory
* @param [ignorePattern] Glob ignore pattern
* @returns string New file prefix
*/
static async getFilePrefix(
filenameFormat: string,
directory: string,
ignorePattern?: string
): Promise<string> {
if (filenameFormat === FilenameFormat.index) {
const filePaths = await getMigrationFilePaths(directory, {
ignorePattern,
useGlob: /\*/.test(directory) || /\*/.test(ignorePattern || ''),
});

// Get the minimum last found prefix as the total number of matching files
let lastPrefix = filePaths.length;

// Index can be used only when there are no mismatching filenames, so all
// the filenames have to be verified first against "index" naming pattern
for (const filenamePath of filePaths) {
const filename = basename(filenamePath);
if (!/^\d{1,4}\D/.test(filename)) {
throw new Error(
`Cannot deduce index for previously created file "${filenamePath}"`
);
}

lastPrefix = Math.max(lastPrefix, getNumericPrefix(filename));
}

// Next prefix is one more than the last found prefix
return `${lastPrefix + 1}`.padStart(4, '0');
}

return filenameFormat === FilenameFormat.utc
? new Date().toISOString().replace(/\D/g, '')
: Date.now().toString();
}

// Class method that creates a new migration file by cloning the migration template
static async create(
name: string,
directory: string,
options: CreateOptions = {}
): Promise<string> {
const { filenameFormat = FilenameFormat.timestamp } = options;
const { filenameFormat = FilenameFormat.timestamp, ignorePattern } =
options;

// ensure the migrations directory exists
// Ensure the migrations directory exists
await mkdir(directory, { recursive: true });

const now = new Date();
const time =
filenameFormat === FilenameFormat.utc
? now.toISOString().replace(/\D/g, '')
: now.valueOf();
// Get prefix based on the configured naming scheme
const prefix = await Migration.getFilePrefix(
filenameFormat,
directory,
ignorePattern
);

const templateFileName =
'templateFileName' in options
Expand All @@ -262,7 +318,7 @@ export class Migration implements RunMigration {
const suffix = getSuffixFromFileName(templateFileName);

// file name looks like migrations/1391877300255_migration-title.js
const newFile = join(directory, `${time}${SEPARATOR}${name}.${suffix}`);
const newFile = join(directory, `${prefix}${SEPARATOR}${name}.${suffix}`);

// copy the default migration template to the new file location
await new Promise<void>((resolve, reject) => {
Expand Down
21 changes: 21 additions & 0 deletions test/migration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { RunnerOption } from '../src';
import type { DBConnection } from '../src/db';
import type { Logger } from '../src/logger';
import {
FilenameFormat,
getMigrationFilePaths,
getNumericPrefix,
Migration,
Expand Down Expand Up @@ -53,6 +54,11 @@ describe('migration', () => {
getNumericPrefix(now.toISOString().replace(/\D/g, ''), logger)
).toBe(now.valueOf());
});

it('should get prefix for index strings', () => {
expect(getNumericPrefix('0001', logger)).toBe(1);
expect(getNumericPrefix('1234', logger)).toBe(1234);
});
});

describe('getMigrationFilePaths', () => {
Expand Down Expand Up @@ -126,6 +132,21 @@ describe('migration', () => {
expect(isAbsolute(filePath)).toBeTruthy();
}
});

it('should resolve the next index for file paths', 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 nextPrefix = await Migration.getFilePrefix(
FilenameFormat.index,
dir,
ignorePattern
);

// There are 106 files matching the pattern
expect(nextPrefix).toEqual('0107');
});
});

describe('self.applyUp', () => {
Expand Down
Loading