Skip to content

Commit

Permalink
feat(core): Add compatibility check to VendurePlugin metadata
Browse files Browse the repository at this point in the history
Relates to #1471
  • Loading branch information
michaelbromley committed Apr 6, 2023
1 parent b2a910a commit d18d350
Show file tree
Hide file tree
Showing 16 changed files with 79 additions and 16 deletions.
16 changes: 15 additions & 1 deletion docs/content/plugins/writing-a-vendure-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,19 @@ Now that we've defined the new mutation and we have a resolver capable of handli
export class RandomCatPlugin {}
```

### Step 9: Specify version compatibility

Since Vendure v2.0.0, it is possible for a plugin to specify which versions of Vendure core it is compatible with. This is especially
important if the plugin is intended to be made publicly available via npm or another package registry.

```TypeScript
@VendurePlugin({
// imports: [ etc. ]
compatibility: '^2.0.0'
})
export class RandomCatPlugin {}
```

### Step 8: Add the plugin to the Vendure config

Finally, we need to add an instance of our plugin to the config object with which we bootstrap our Vendure server:
Expand Down Expand Up @@ -280,7 +293,8 @@ export class RandomCatResolver {
name: 'catImageUrl',
});
return config;
}
},
compatibility: '^2.0.0',
})
export class RandomCatPlugin {}
```
1 change: 1 addition & 0 deletions packages/admin-ui-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export interface AdminUiPluginOptions {
@VendurePlugin({
imports: [PluginCommonModule],
providers: [],
compatibility: '^2.0.0-beta.0',
})
export class AdminUiPlugin implements NestModule {
private static options: AdminUiPluginOptions;
Expand Down
5 changes: 3 additions & 2 deletions packages/asset-server-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import { AssetServerOptions, ImageTransformPreset } from './types';
@VendurePlugin({
imports: [PluginCommonModule],
configuration: config => AssetServerPlugin.configure(config),
compatibility: '^2.0.0-beta.0',
})
export class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
private static assetStorage: AssetStorageStrategy;
Expand Down Expand Up @@ -246,7 +247,7 @@ export class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
mimeType = (await fromBuffer(file))?.mime || 'application/octet-stream';
}
res.contentType(mimeType);
res.setHeader('content-security-policy', 'default-src \'self\'');
res.setHeader('content-security-policy', "default-src 'self'");
res.setHeader('Cache-Control', this.cacheHeader);
res.send(file);
} catch (e: any) {
Expand Down Expand Up @@ -291,7 +292,7 @@ export class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
mimeType = (await fromBuffer(imageBuffer))?.mime || 'image/jpeg';
}
res.set('Content-Type', mimeType);
res.setHeader('content-security-policy', 'default-src \'self\'');
res.setHeader('content-security-policy', "default-src 'self'");
res.send(imageBuffer);
return;
} catch (e: any) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"@types/node": "^14.14.31",
"@types/progress": "^2.0.3",
"@types/prompts": "^2.0.9",
"@types/semver": "^7.3.13",
"better-sqlite3": "^7.1.1",
"gulp": "^4.0.2",
"mysql": "^2.18.1",
Expand Down
37 changes: 28 additions & 9 deletions packages/core/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NestFactory } from '@nestjs/core';
import { getConnectionToken } from '@nestjs/typeorm';
import { Type } from '@vendure/common/lib/shared-types';
import cookieSession = require('cookie-session');
import { satisfies } from 'semver';
import { Connection, DataSourceOptions, EntitySubscriberInterface } from 'typeorm';

import { InternalServerError } from './common/error/errors';
Expand All @@ -17,9 +18,10 @@ import { runEntityMetadataModifiers } from './entity/run-entity-metadata-modifie
import { setEntityIdStrategy } from './entity/set-entity-id-strategy';
import { setMoneyStrategy } from './entity/set-money-strategy';
import { validateCustomFieldsConfig } from './entity/validate-custom-fields-config';
import { getConfigurationFunction, getEntitiesFromPlugins } from './plugin/plugin-metadata';
import { getCompatibility, getConfigurationFunction, getEntitiesFromPlugins } from './plugin/plugin-metadata';
import { getPluginStartupMessages } from './plugin/plugin-utils';
import { setProcessContext } from './process-context/process-context';
import { VENDURE_VERSION } from './version';
import { VendureWorker } from './worker/vendure-worker';

export type VendureBootstrapFunction = (config: VendureConfig) => Promise<INestApplication>;
Expand All @@ -43,6 +45,7 @@ export async function bootstrap(userConfig: Partial<VendureConfig>): Promise<INe
const config = await preBootstrapConfig(userConfig);
Logger.useLogger(config.logger);
Logger.info(`Bootstrapping Vendure Server (pid: ${process.pid})...`);
checkPluginCompatibility(config);

// The AppModule *must* be loaded only after the entities have been set in the
// config, so that they are available when the AppModule decorator is evaluated.
Expand Down Expand Up @@ -102,6 +105,7 @@ export async function bootstrapWorker(userConfig: Partial<VendureConfig>): Promi
config.logger.setDefaultContext?.('Vendure Worker');
Logger.useLogger(config.logger);
Logger.info(`Bootstrapping Vendure Worker (pid: ${process.pid})...`);
checkPluginCompatibility(config);

setProcessContext('worker');
DefaultLogger.hideNestBoostrapLogs();
Expand Down Expand Up @@ -159,6 +163,28 @@ export async function preBootstrapConfig(
return config;
}

function checkPluginCompatibility(config: RuntimeVendureConfig): void {
for (const plugin of config.plugins) {
const compatibility = getCompatibility(plugin);
const pluginName = (plugin as any).name as string;
if (!compatibility) {
Logger.info(
`The plugin "${pluginName}" does not specify a compatibility range, so it is not guaranteed to be compatible with this version of Vendure.`,
);
} else {
if (!satisfies(VENDURE_VERSION, compatibility)) {
Logger.error(
`Plugin "${pluginName}" is not compatible with this version of Vendure. ` +
`It specifies a semver range of "${compatibility}" but the current version is "${VENDURE_VERSION}".`,
);
throw new InternalServerError(
`Plugin "${pluginName}" is not compatible with this version of Vendure.`,
);
}
}
}
}

/**
* Initialize any configured plugins.
*/
Expand Down Expand Up @@ -223,13 +249,6 @@ function setExposedHeaders(config: Readonly<RuntimeVendureConfig>) {
}

function logWelcomeMessage(config: RuntimeVendureConfig) {
let version: string;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
version = require('../package.json').version;
} catch (e: any) {
version = ' unknown';
}
const { port, shopApiPath, adminApiPath, hostname } = config.apiOptions;
const apiCliGreetings: Array<readonly [string, string]> = [];
const pathToUrl = (path: string) => `http://${hostname || 'localhost'}:${port}/${path}`;
Expand All @@ -239,7 +258,7 @@ function logWelcomeMessage(config: RuntimeVendureConfig) {
...getPluginStartupMessages().map(({ label, path }) => [label, pathToUrl(path)] as const),
);
const columnarGreetings = arrangeCliGreetingsInColumns(apiCliGreetings);
const title = `Vendure server (v${version}) now running on port ${port}`;
const title = `Vendure server (v${VENDURE_VERSION}) now running on port ${port}`;
const maxLineLength = Math.max(title.length, ...columnarGreetings.map(l => l.length));
const titlePadLength = title.length < maxLineLength ? Math.floor((maxLineLength - title.length) / 2) : 0;
Logger.info('='.repeat(maxLineLength));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export interface DefaultJobQueueOptions {
}
return config;
},
compatibility: '*',
})
export class DefaultJobQueuePlugin {
/** @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export interface DefaultSearchReindexResponse extends SearchReindexResponse {
resolvers: [ShopFulltextSearchResolver],
},
entities: [SearchIndexItem],
compatibility: '*',
})
export class DefaultSearchPlugin implements OnApplicationBootstrap, OnApplicationShutdown {
static options: DefaultSearchPluginInitOptions = {};
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/plugin/plugin-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const PLUGIN_METADATA = {
SHOP_API_EXTENSIONS: 'shopApiExtensions',
ADMIN_API_EXTENSIONS: 'adminApiExtensions',
ENTITIES: 'entities',
COMPATIBILITY: 'compatibility',
};

export function getEntitiesFromPlugins(plugins?: Array<Type<any> | DynamicModule>): Array<Type<any>> {
Expand Down Expand Up @@ -45,8 +46,8 @@ export function getPluginAPIExtensions(
return extensions.filter(notNullOrUndefined);
}

export function getPluginModules(plugins: Array<Type<any> | DynamicModule>): Array<Type<any>> {
return plugins.map(p => (isDynamicModule(p) ? p.module : p));
export function getCompatibility(plugin: Type<any> | DynamicModule): string | undefined {
return reflectMetadata(plugin, PLUGIN_METADATA.COMPATIBILITY);
}

export function getConfigurationFunction(
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/plugin/vendure-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ export interface VendurePluginMetadata extends ModuleMetadata {
* The plugin may define custom [TypeORM database entities](https://typeorm.io/#/entities).
*/
entities?: Array<Type<any>> | (() => Array<Type<any>>);
/**
* @description
* The plugin should define a valid [semver version string](https://www.npmjs.com/package/semver) to indicate which versions of
* Vendure core it is compatible with. Attempting to use a plugin with an incompatible
* version of Vendure will result in an error and the server will be unable to bootstrap.
*
* If a plugin does not define this property, a message will be logged on bootstrap that the plugin is not
* guaranteed to be compatible with the current version of Vendure.
*
* To effectively disable this check for a plugin, you can use an overly-permissive string such as `*`.
*
* @example
* ```typescript
* compatibility: '^2.0.0'
* ```
*
* @since 2.0.0
*/
compatibility?: string;
}
/**
* @description
Expand Down
2 changes: 1 addition & 1 deletion packages/dev-server/dev-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ function getDbConfig(): DataSourceOptions {
port: 3306,
username: 'root',
password: '',
database: 'vendure-dev',
database: 'vendure2-dev',
};
}
}
1 change: 1 addition & 0 deletions packages/elasticsearch-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ function getCustomResolvers(options: ElasticsearchRuntimeOptions) {
// which looks like possibly a TS/definitions bug.
schema: () => generateSchemaExtensions(ElasticsearchPlugin.options as any),
},
compatibility: '^2.0.0-beta.0',
})
export class ElasticsearchPlugin implements OnApplicationBootstrap {
private static options: ElasticsearchRuntimeOptions;
Expand Down
1 change: 1 addition & 0 deletions packages/email-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ import {
@VendurePlugin({
imports: [PluginCommonModule],
providers: [{ provide: EMAIL_PLUGIN_OPTIONS, useFactory: () => EmailPlugin.options }, EmailProcessor],
compatibility: '^2.0.0-beta.0',
})
export class EmailPlugin implements OnApplicationBootstrap, OnApplicationShutdown, NestModule {
private static options: EmailPluginOptions | EmailPluginDevModeOptions;
Expand Down
1 change: 1 addition & 0 deletions packages/harden-plugin/src/harden.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ import { HardenPluginOptions } from './types';

return config;
},
compatibility: '^2.0.0-beta.0',
})
export class HardenPlugin {
static options: HardenPluginOptions;
Expand Down
1 change: 1 addition & 0 deletions packages/job-queue-plugin/src/bullmq/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import { BullMQPluginOptions } from './types';
{ provide: BULLMQ_PLUGIN_OPTIONS, useFactory: () => BullMQJobQueuePlugin.options },
RedisHealthIndicator,
],
compatibility: '^2.0.0-beta.0',
})
export class BullMQJobQueuePlugin {
static options: BullMQPluginOptions;
Expand Down
1 change: 1 addition & 0 deletions packages/job-queue-plugin/src/pub-sub/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { PubSubJobQueueStrategy } from './pub-sub-job-queue-strategy';
config.jobQueueOptions.jobQueueStrategy = new PubSubJobQueueStrategy();
return config;
},
compatibility: '^2.0.0-beta.0',
})
export class PubSubPlugin {
private static options: PubSubOptions;
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4316,7 +4316,7 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.2.3.tgz#5798ecf1bec94eaa64db39ee52808ec0693315aa"
integrity sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==

"@types/semver@^7.3.12":
"@types/semver@^7.3.12", "@types/semver@^7.3.13":
version "7.3.13"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
Expand Down

0 comments on commit d18d350

Please sign in to comment.