@vendure/core
Version:
A modern, headless ecommerce framework
296 lines • 13.2 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PluginCollector = void 0;
exports.isVendurePluginPackage = isVendurePluginPackage;
const common_1 = require("@nestjs/common");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const config_service_1 = require("../../config/config.service");
const plugin_metadata_1 = require("../../plugin/plugin-metadata");
/**
* Known Vendure plugins mapped to their npm package names.
* This is more reliable than require.cache inspection which fails with ESM/TypeScript.
*/
const KNOWN_VENDURE_PLUGINS = {
// @vendure/core
DefaultSearchPlugin: '@vendure/core',
DefaultJobQueuePlugin: '@vendure/core',
DefaultSchedulerPlugin: '@vendure/core',
// @vendure/asset-server-plugin
AssetServerPlugin: '@vendure/asset-server-plugin',
// @vendure/email-plugin
EmailPlugin: '@vendure/email-plugin',
// @vendure/admin-ui-plugin
AdminUiPlugin: '@vendure/admin-ui-plugin',
// @vendure/dashboard
DashboardPlugin: '@vendure/dashboard',
// @vendure/job-queue-plugin
BullMQJobQueuePlugin: '@vendure/job-queue-plugin',
// @vendure/graphiql-plugin
GraphiqlPlugin: '@vendure/graphiql-plugin',
// @vendure/harden-plugin
HardenPlugin: '@vendure/harden-plugin',
// Community plugins (moved to @vendure-community/*)
ElasticsearchPlugin: '@vendure-community/elasticsearch-plugin',
SentryPlugin: '@vendure-community/sentry-plugin',
StripePlugin: '@vendure-community/stripe-plugin',
MolliePlugin: '@vendure-community/mollie-plugin',
BraintreePlugin: '@vendure-community/braintree-plugin',
};
/**
* npm package names that identify official Vendure plugins, derived from the
* known-plugin map above so there is a single source of truth.
*/
const KNOWN_VENDURE_PACKAGES = new Set(Object.values(KNOWN_VENDURE_PLUGINS));
/**
* Determines whether an npm package name belongs to the Vendure plugin
* ecosystem. Deliberately restricted to packages published on the public npm
* registry under the official (`@vendure/*-plugin`, plus `@vendure/core`) and
* community (`@vendure-community/*`) scopes.
*
* Arbitrary third-party or privately-named packages are intentionally NOT
* matched here, so that scanning the host `package.json` can never transmit a
* private or internal package name — preserving the guarantee that custom
* plugin names are not collected. Such third-party plugins are still detected
* by package name via require.cache when they are actually loaded under
* CommonJS.
*/
function isVendurePluginPackage(name) {
return (KNOWN_VENDURE_PACKAGES.has(name) ||
name.startsWith('@vendure-community/') ||
(name.startsWith('@vendure/') && name.endsWith('-plugin')));
}
/**
* Collects information about plugins used in the Vendure installation.
* Detects npm packages by checking if the plugin originates from node_modules.
* Custom plugin names are NOT collected for privacy.
*/
let PluginCollector = class PluginCollector {
constructor(configService) {
this.configService = configService;
}
collect() {
try {
const plugins = this.configService.plugins;
const npmPlugins = new Set();
let customCount = 0;
for (const plugin of plugins) {
try {
const npmPackage = this.findNpmPackage(plugin);
if (npmPackage) {
npmPlugins.add(npmPackage);
}
else {
customCount++;
}
}
catch (_a) {
customCount++;
}
}
// Also record Vendure ecosystem packages declared in the host
// package.json. This filesystem-based detection is ESM-safe and
// catches official/community plugin packages (and @vendure/core)
// that require.cache inspection misses when modules are loaded as
// native ESM. Only public ecosystem packages are matched (see
// isVendurePluginPackage), so no private package name is ever sent.
for (const pkg of this.getDeclaredVendurePackages()) {
npmPlugins.add(pkg);
}
return {
npm: Array.from(npmPlugins).sort((a, b) => a.localeCompare(b)),
customCount,
};
}
catch (_b) {
return { npm: [], customCount: 0 };
}
}
/**
* Reads every `package.json` found by walking up from each search directory
* and returns the names of declared Vendure plugin packages. Relies only on
* the filesystem, so it works regardless of whether plugins were loaded via
* CommonJS or native ESM.
*
* Monorepo-aware: it merges manifests up the tree (stopping at a project
* boundary) and searches from both the current working directory and the
* application entry point. This covers workspace layouts where plugin
* dependencies live in a sub-package and/or the repository root, and where
* the process is started from a different directory than the app package.
*
* Only runtime dependency sections are scanned (`dependencies` and
* `optionalDependencies`); `devDependencies` are excluded since they are
* not runtime plugins. Returns an empty array on any failure.
*/
getDeclaredVendurePackages(searchDirs = this.getManifestSearchDirs()) {
const found = new Set();
const visited = new Set();
for (const startDir of searchDirs) {
for (const pkgPath of this.findPackageJsonPaths(startDir)) {
if (visited.has(pkgPath)) {
continue;
}
visited.add(pkgPath);
for (const name of this.readVendurePackagesFromManifest(pkgPath)) {
found.add(name);
}
}
}
return Array.from(found);
}
/**
* Parses a single `package.json` and returns the Vendure ecosystem package
* names declared in its runtime dependency sections. Returns an empty array
* if the manifest cannot be read or parsed.
*/
readVendurePackagesFromManifest(pkgPath) {
var _a, _b;
try {
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
const depNames = [
...Object.keys((_a = pkg.dependencies) !== null && _a !== void 0 ? _a : {}),
...Object.keys((_b = pkg.optionalDependencies) !== null && _b !== void 0 ? _b : {}),
];
return depNames.filter(isVendurePluginPackage);
}
catch (_c) {
return [];
}
}
/**
* The directories from which to search for package.json manifests: the
* current working directory (the primary signal) and, when resolvable, the
* directory of the application entry point — which in a monorepo may sit in
* a different workspace package than the cwd. Deduplicated.
*/
getManifestSearchDirs() {
var _a;
const dirs = new Set([process.cwd()]);
const mainFile = typeof require === 'undefined' ? undefined : (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename;
const entryFile = mainFile !== null && mainFile !== void 0 ? mainFile : process.argv[1];
if (entryFile) {
dirs.add(node_path_1.default.dirname(entryFile));
}
return Array.from(dirs);
}
/**
* Returns the paths of all `package.json` files found by walking up from
* `startDir`, stopping at a project boundary — a directory containing a
* `.git` entry (repo root) or a `node_modules` directory (install /
* workspace root). Both markers exist in real deployments, so the walk
* stays inside the project rather than reading unrelated ancestor
* manifests. Bounded to a fixed depth as a final safety net.
*/
findPackageJsonPaths(startDir) {
const paths = [];
let dir = startDir;
for (let i = 0; i < 15; i++) {
const candidate = node_path_1.default.join(dir, 'package.json');
if (node_fs_1.default.existsSync(candidate)) {
paths.push(candidate);
}
if (node_fs_1.default.existsSync(node_path_1.default.join(dir, '.git')) || node_fs_1.default.existsSync(node_path_1.default.join(dir, 'node_modules'))) {
break;
}
const parent = node_path_1.default.dirname(dir);
if (parent === dir) {
break;
}
dir = parent;
}
return paths;
}
/**
* Finds the npm package name for a plugin.
* First checks against known Vendure plugins, then falls back to require.cache inspection.
*/
findNpmPackage(plugin) {
var _a;
const pluginClass = (0, plugin_metadata_1.isDynamicModule)(plugin) ? plugin.module : plugin;
if (!pluginClass) {
return undefined;
}
const pluginName = (_a = pluginClass.name) !== null && _a !== void 0 ? _a : 'unknown';
// First, check against known Vendure plugins (most reliable)
const knownPackage = KNOWN_VENDURE_PLUGINS[pluginName];
if (knownPackage) {
return knownPackage;
}
// Fall back to require.cache inspection for third-party npm plugins
return this.findInRequireCache(pluginClass);
}
/**
* Searches the require cache for a plugin class.
* This is a fallback for third-party npm plugins not in our known list.
*/
findInRequireCache(pluginClass) {
// Check if require.cache is available (may not be in ESM-only environments)
if (typeof require === 'undefined' || !require.cache) {
return undefined;
}
try {
for (const [modulePath, moduleObj] of Object.entries(require.cache)) {
if (!(moduleObj === null || moduleObj === void 0 ? void 0 : moduleObj.exports) || !modulePath.includes('node_modules')) {
continue;
}
try {
const exports = moduleObj.exports;
// Direct match or default export match
if (exports === pluginClass || (exports === null || exports === void 0 ? void 0 : exports.default) === pluginClass) {
return this.extractPackageName(modulePath);
}
// Check named exports
if (typeof exports === 'object' && exports !== null) {
const exportValues = Object.values(exports);
if (exportValues.includes(pluginClass)) {
return this.extractPackageName(modulePath);
}
}
}
catch (_a) {
// Skip modules with problematic exports
continue;
}
}
}
catch (_b) {
// Ignore errors accessing require.cache
}
return undefined;
}
/**
* Extracts the npm package name from a node_modules path.
* Handles both scoped (@scope/package) and unscoped packages.
*/
extractPackageName(modulePath) {
const nodeModulesIndex = modulePath.lastIndexOf('node_modules');
if (nodeModulesIndex === -1) {
return undefined;
}
const pathAfterNodeModules = modulePath.slice(nodeModulesIndex + 'node_modules/'.length);
const parts = pathAfterNodeModules.split(/[/\\]/);
if (parts[0].startsWith('@')) {
// Scoped package: @scope/package
return `${parts[0]}/${parts[1]}`;
}
return parts[0];
}
};
exports.PluginCollector = PluginCollector;
exports.PluginCollector = PluginCollector = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [config_service_1.ConfigService])
], PluginCollector);
//# sourceMappingURL=plugin.collector.js.map