Skip to content

Commit

Permalink
feat(core): validate version mismatch in ORM packages
Browse files Browse the repository at this point in the history
Can be disabled via `MIKRO_ORM_ALLOW_VERSION_MISMATCH` env var.
  • Loading branch information
B4nan committed Feb 5, 2022
1 parent 92ea445 commit cf70219
Show file tree
Hide file tree
Showing 24 changed files with 549 additions and 430 deletions.
34 changes: 34 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Config } from '@jest/types';

// Sync object
const config: Config.InitialOptions = {
testTimeout: 30000,
preset: 'ts-jest',
collectCoverage: false,
collectCoverageFrom: [
'<rootDir>/packages/*/src/**/*.ts',
],
moduleNameMapper: {
'@mikro-orm/mongo-highlighter': '<rootDir>/node_modules/@mikro-orm/mongo-highlighter',
'@mikro-orm/sql-highlighter': '<rootDir>/node_modules/@mikro-orm/sql-highlighter',
'@mikro-orm/(.*)/package.json': '<rootDir>/packages/$1/package.json',
'@mikro-orm/(.*)': '<rootDir>/packages/$1/src',
},
modulePathIgnorePatterns: [
'dist/package.json',
'<rootDir>/package.json',
],
coveragePathIgnorePatterns: [
'<rootDir>/packages/cli/src/cli.ts',
],
setupFiles: [
'<rootDir>/tests/setup.ts',
],
globals: {
'ts-jest': {
tsconfig: 'tests/tsconfig.json',
},
},
};

export default config;
30 changes: 1 addition & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,34 +52,6 @@
"run-rs-in-background": "yarn run-rs > /dev/null 2>&1 &",
"lint": "eslint packages/**/*.ts"
},
"jest": {
"testTimeout": 30000,
"preset": "ts-jest",
"collectCoverage": false,
"collectCoverageFrom": [
"<rootDir>/packages/*/src/**/*.ts"
],
"moduleNameMapper": {
"@mikro-orm/mongo-highlighter": "<rootDir>/node_modules/@mikro-orm/mongo-highlighter",
"@mikro-orm/sql-highlighter": "<rootDir>/node_modules/@mikro-orm/sql-highlighter",
"@mikro-orm/(.*)": "<rootDir>/packages/$1/src"
},
"modulePathIgnorePatterns": [
"dist/package.json",
"<rootDir>/package.json"
],
"coveragePathIgnorePatterns": [
"<rootDir>/packages/cli/src/cli.ts"
],
"setupFiles": [
"<rootDir>/tests/setup.ts"
],
"globals": {
"ts-jest": {
"tsconfig": "tests/tsconfig.json"
}
}
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
Expand Down Expand Up @@ -141,7 +113,7 @@
"gen-esm-wrapper": "1.1.3",
"guid-typescript": "1.0.9",
"husky": "7.0.4",
"jest": "27.4.7",
"jest": "27.5.0",
"lerna": "4.0.0",
"lint-staged": "12.3.3",
"mongodb": "4.3.1",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/CLIConfigurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import { CreateDatabaseCommand } from './commands/CreateDatabaseCommand';
export class CLIConfigurator {

static async configure(): Promise<Argv> {
await ConfigurationLoader.checkPackageVersion();
const settings = await ConfigurationLoader.getSettings();
const version = await Utils.getORMVersion();

if (settings.useTsNode) {
await ConfigurationLoader.registerTsNode(settings.tsConfigPath);
Expand All @@ -28,7 +30,7 @@ export class CLIConfigurator {
// noinspection HtmlDeprecatedTag
return yargs
.scriptName('mikro-orm')
.version(Utils.getORMVersion())
.version(version)
.usage('Usage: $0 <command> [options]')
.example('$0 schema:update --run', 'Runs schema synchronization')
.alias('v', 'version')
Expand Down
9 changes: 3 additions & 6 deletions packages/cli/src/CLIHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ import { colors, ConfigurationLoader, MikroORM, Utils } from '@mikro-orm/core';
export class CLIHelper {

static async getConfiguration<D extends IDatabaseDriver = IDatabaseDriver>(validate = true, options: Partial<Options> = {}): Promise<Configuration<D>> {
const pkg = await ConfigurationLoader.getPackageConfig();
const deps = new Set([
...Object.keys(pkg.dependencies ?? {}),
...Object.keys(pkg.devDependencies ?? {}),
]);
const deps = await ConfigurationLoader.getORMPackages();

if (!deps.has('@mikro-orm/cli') && !process.env.MIKRO_ORM_ALLOW_GLOBAL_CLI) {
throw new Error('@mikro-orm/cli needs to be installed as a local dependency!');
Expand Down Expand Up @@ -68,8 +64,9 @@ export class CLIHelper {
}

static async dumpDependencies() {
const version = await Utils.getORMVersion();
CLIHelper.dump(' - dependencies:');
CLIHelper.dump(` - mikro-orm ${colors.green(Utils.getORMVersion())}`);
CLIHelper.dump(` - mikro-orm ${colors.green(version)}`);
CLIHelper.dump(` - node ${colors.green(CLIHelper.getNodeVersion())}`);

if (await pathExists(process.cwd() + '/package.json')) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/MikroORM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class MikroORM<D extends IDatabaseDriver = IDatabaseDriver> {
* If you omit the `options` parameter, your CLI config will be used.
*/
static async init<D extends IDatabaseDriver = IDatabaseDriver>(options?: Options<D> | Configuration<D>, connect = true): Promise<MikroORM<D>> {
const coreVersion = await ConfigurationLoader.checkPackageVersion();
const env = ConfigurationLoader.loadEnvironmentVars<D>(options);

if (!options) {
Expand All @@ -34,6 +35,7 @@ export class MikroORM<D extends IDatabaseDriver = IDatabaseDriver> {

options = options instanceof Configuration ? options.getAll() : options;
const orm = new MikroORM<D>(Utils.merge(options, env));
orm.logger.log('info', `MikroORM version: ${colors.green(coreVersion)}`);

// we need to allow global context here as we are not in a scope of requests yet
const allowGlobalContext = orm.config.get('allowGlobalContext');
Expand Down
46 changes: 46 additions & 0 deletions packages/core/src/utils/ConfigurationLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Options } from './Configuration';
import { Configuration } from './Configuration';
import { Utils } from './Utils';
import type { Dictionary } from '../typings';
import { colors } from '../logging/colors';

/**
* @internal
Expand Down Expand Up @@ -199,6 +200,51 @@ export class ConfigurationLoader {
return ret;
}

static async getORMPackages(): Promise<Set<string>> {
const pkg = await this.getPackageConfig();
return new Set([
...Object.keys(pkg.dependencies ?? {}),
...Object.keys(pkg.devDependencies ?? {}),
]);
}

static async getORMPackageVersion(name: string): Promise<string | undefined> {
/* istanbul ignore next */
try {
const pkg = await Utils.dynamicImport<{ version?: string }>(`${name}/package.json`);
return pkg?.version;
} catch (e) {
return undefined;
}
}

// inspired by https://github.com/facebook/mikro-orm/pull/3386
static async checkPackageVersion(): Promise<string> {
const coreVersion = await Utils.getORMVersion();
const deps = await this.getORMPackages();
const exceptions = new Set(['nestjs', 'sql-highlighter', 'mongo-highlighter']);
const ormPackages = [...deps].filter(d => d.startsWith('@mikro-orm/') && !exceptions.has(d.substring('@mikro-orm/'.length)));

if (process.env.MIKRO_ORM_ALLOW_VERSION_MISMATCH) {
return coreVersion;
}

for (const ormPackage of ormPackages) {
const version = await this.getORMPackageVersion(ormPackage);

if (version !== coreVersion) {
throw new Error(
`Bad ${colors.cyan(ormPackage)} version ${colors.yellow('' + version)}.\n` +
`All official @mikro-orm/* packages need to have the exact same version as @mikro-orm/core (${colors.green(coreVersion)}).\n` +
`Only exceptions are packages that don't live in the 'mikro-orm' repository: ${[...exceptions].join(', ')}.\n` +
`Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`,
);
}
}

return coreVersion;
}

}

export interface Settings {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,16 +813,16 @@ export class Utils {
return Function(`return import('${id}')`)();
}

static getORMVersion(): string {
static async getORMVersion(): Promise<string> {
/* istanbul ignore next */
try {
// this works with ts-node during development (where we have `src` folder)
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('../../package.json').version;
const pkg = await this.dynamicImport('../../package.json');
return pkg.version;
} catch {
// this works with node in production build (where we do not have the `src` folder)
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('../package.json').version;
const pkg = await this.dynamicImport('../package.json');
return pkg.version;
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/entity-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/knex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/mariadb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/migrations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/mikro-orm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/mongodb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/mysql-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/mysql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
3 changes: 2 additions & 1 deletion packages/postgresql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down Expand Up @@ -56,7 +57,7 @@
},
"dependencies": {
"@mikro-orm/knex": "^5.0.0-rc.2",
"pg": "8.7.1"
"pg": "8.7.3"
},
"devDependencies": {
"@mikro-orm/core": "^5.0.0-rc.2"
Expand Down
1 change: 1 addition & 0 deletions packages/reflection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/sqlite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
Expand Down
5 changes: 3 additions & 2 deletions tests/MikroORM.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jest.mock('knex', () => ({ knex }));

(global as any).process.env.FORCE_COLOR = 0;

import { Configuration, EntityManager, MikroORM, NullCacheAdapter } from '@mikro-orm/core';
import { Configuration, EntityManager, MikroORM, NullCacheAdapter, Utils } from '@mikro-orm/core';
import fs from 'fs-extra';
import { BASE_DIR } from './helpers';
import { Author, Test } from './entities';
Expand Down Expand Up @@ -198,7 +198,8 @@ describe('MikroORM', () => {
debug: ['info'],
logger,
});
expect(logger.mock.calls[0][0]).toEqual('[info] MikroORM failed to connect to database not-found on mysql://[email protected]:3306');
expect(logger.mock.calls[0][0]).toEqual(`[info] MikroORM version: ${await Utils.getORMVersion()}`);
expect(logger.mock.calls[1][0]).toEqual('[info] MikroORM failed to connect to database not-found on mysql://[email protected]:3306');
});

test('orm.close() calls CacheAdapter.close()', async () => {
Expand Down
29 changes: 29 additions & 0 deletions tests/features/cli/CLIHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ describe('CLIHelper', () => {
Object.keys(process.env).filter(k => k.startsWith('MIKRO_ORM_')).forEach(k => delete process.env[k]);
process.env.MIKRO_ORM_ALLOW_GLOBAL_CONTEXT = '1';
process.env.MIKRO_ORM_ALLOW_GLOBAL_CLI = '1';
process.env.MIKRO_ORM_ALLOW_VERSION_MISMATCH = '1';
});

test('disallows global install of CLI package', async () => {
Expand All @@ -132,6 +133,34 @@ describe('CLIHelper', () => {
process.env.MIKRO_ORM_ALLOW_GLOBAL_CLI = '1';
});

test('disallows version mismatch of ORM packages', async () => {
delete process.env.MIKRO_ORM_ALLOW_VERSION_MISMATCH;
const spy = jest.spyOn(ConfigurationLoader, 'getORMPackages');
spy.mockResolvedValueOnce(new Set(['@mikro-orm/weird-package']));
const spy3 = jest.spyOn(Utils, 'getORMVersion');
spy3.mockResolvedValue('5.0.0');

await expect(ConfigurationLoader.checkPackageVersion()).rejects.toThrowError(`Bad @mikro-orm/weird-package version undefined.
All official @mikro-orm/* packages need to have the exact same version as @mikro-orm/core (5.0.0).
Only exceptions are packages that don't live in the 'mikro-orm' repository: nestjs, sql-highlighter, mongo-highlighter.
Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`);

spy.mockResolvedValueOnce(new Set(['@mikro-orm/weird-package']));
const spy2 = jest.spyOn(ConfigurationLoader, 'getORMPackageVersion');
spy2.mockResolvedValueOnce('1.2.3');

await expect(ConfigurationLoader.checkPackageVersion()).rejects.toThrowError(`Bad @mikro-orm/weird-package version 1.2.3.
All official @mikro-orm/* packages need to have the exact same version as @mikro-orm/core (5.0.0).
Only exceptions are packages that don't live in the 'mikro-orm' repository: nestjs, sql-highlighter, mongo-highlighter.
Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`);

await expect(ConfigurationLoader.checkPackageVersion()).resolves.toMatch(/^\d+\.\d+\.\d+/);
process.env.MIKRO_ORM_ALLOW_VERSION_MISMATCH = '1';
spy.mockRestore();
spy2.mockRestore();
spy3.mockRestore();
});

test('registerTsNode works with tsconfig.json with comments', async () => {
const requireFromMock = jest.spyOn(Utils, 'requireFrom');
const register = jest.fn();
Expand Down
Loading

0 comments on commit cf70219

Please sign in to comment.