@netlify/content-engine
Version:
295 lines • 13.7 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LedgerDependencyManager = void 0;
const exec_1 = __importDefault(require("@pnpm/exec"));
const path_1 = __importDefault(require("path"));
const cache_lmdb_1 = __importDefault(require("../../utils/cache-lmdb"));
const fs_extra_1 = require("fs-extra");
class LedgerDependencyManager {
directory;
cache;
pendingPluginInstallPayloads = new Map();
installedPluginPayloads = new Map();
sitePlugin;
sawAtleastOneGatsbyPlugin = false;
dynamicConnectorIds = new Set();
constructor(input) {
this.directory = input.directory;
this.cache = new cache_lmdb_1.default({
name: `ledger-dependency-manager`,
encoding: `json`,
directory: input.directory,
}).init();
}
get didInstallPlugins() {
return this.installedPluginPayloads.size > 0;
}
checkEngineConfigForPluginInstalls(engineConfig) {
engineConfig.plugins?.forEach((plugin) => {
if (typeof plugin === `string`) {
return;
}
// Mock a PluginActionPayload to install from plugin configs
const pluginPayload = {
// plugin.resolve is the slug of the connector
name: plugin.resolve,
id: plugin.resolve,
nodeAPIs: ["createSchemaCustomization", "sourceNodes", "onPluginInit"],
// This will be used as the url to install the connector from
pluginFilepath: plugin.parentDir,
};
this.pendingPluginInstallPayloads.set(plugin.resolve, pluginPayload);
});
}
checkLedgerActionForPluginInstall(action) {
if (action.type ===
`LEDGER_DEPENDENCY_MANAGER_SHOULD_INSTALL_DYNAMIC_CONNECTOR` &&
action.payload?.plugin?.id) {
this.dynamicConnectorIds.add(action.payload.plugin.id);
}
const isInstallablePlugin = this.isInstallablePluginAction(action);
if (isInstallablePlugin) {
this.pendingPluginInstallPayloads.set(action.payload.id, action.payload);
}
if (this.isDefaultSitePlugin(action.payload)) {
this.sitePlugin = action.payload;
}
return {
isInstallablePlugin,
};
}
getEnginePluginsConfig() {
const plugins = Array.from(this.installedPluginPayloads.values()).map((pluginPayload) => {
return {
resolve: pluginPayload.name,
options: pluginPayload.pluginOptions,
};
});
if (process.env.NODE_ENV !== "production") {
(0, fs_extra_1.writeFileSync)(path_1.default.join(this.directory, "engine-config-debug.json"), JSON.stringify(plugins, null, 2), `utf-8`);
}
return plugins;
}
async persist() {
if (this.sitePlugin) {
await this.cache.set(`site-plugin`, this.sitePlugin);
}
if (this.installedPluginPayloads.size > 0) {
await this.cache.set(`installed-plugins`, Array.from(this.installedPluginPayloads.entries()));
}
}
async restore() {
if (!this.sitePlugin) {
this.sitePlugin = (await this.cache.get(`site-plugin`)) || undefined;
}
if (this.installedPluginPayloads.size === 0) {
const cachedInstalls = await this.cache.get(`installed-plugins`);
this.installedPluginPayloads = new Map(Array.isArray(cachedInstalls) ? cachedInstalls : []);
}
}
async installPendingPluginsForWriter() {
await this.restore();
if (this.pendingPluginInstallPayloads.size === 0) {
return {
totalInstalled: 0,
previouslyInstalled: this.installedPluginPayloads.size,
};
}
this.pendingPluginInstallPayloads.forEach((plugin, id) => {
// we store each of these for later because they have pluginOptions on them and we need to configure them after they're installed
this.installedPluginPayloads.set(id, plugin);
});
await this.persist();
const pnpmArgs = this.getPnpmAddArgumentsForWriter();
if (pnpmArgs.length > 0) {
console.info(`Running "pnpm add ${pnpmArgs.join(` `)}" in ${this.directory}`);
await (0, exec_1.default)([`add`, ...pnpmArgs], {
cwd: this.directory,
});
this.pendingPluginInstallPayloads.clear();
console.info(`Finished installing Content Engine plugins`);
}
return {
totalInstalled: this.installedPluginPayloads.size,
previouslyInstalled: 0,
};
}
async installPendingPlugins() {
await this.restore();
this.removeUnneededGatsbyPlugins();
this.removeExtraPendingConnectorInstalls();
if (this.pendingPluginInstallPayloads.size === 0) {
await this.persist();
return {
totalInstalled: 0,
previouslyInstalled: this.installedPluginPayloads.size,
};
}
const pnpmAddArgs = this.getPnpmAddArguments();
this.pendingPluginInstallPayloads.forEach((plugin, id) => {
// we store each of these for later because they have pluginOptions on them and we need to configure them after they're installed
this.installedPluginPayloads.set(id, plugin);
});
await this.persist();
this.pendingPluginInstallPayloads.clear();
if (pnpmAddArgs.length > 0) {
if (this.sawAtleastOneGatsbyPlugin) {
const thisContentEnginePath = path_1.default.dirname(require.resolve(`@netlify/content-engine/package.json`));
// override gatsby with content-engine if we have a gatsby source plugin present.
pnpmAddArgs.push(`gatsby@link:${thisContentEnginePath}`);
}
console.info(`Running "pnpm add ${pnpmAddArgs.join(` `)}" in ${this.directory}`);
await (0, exec_1.default)([`add`, ...pnpmAddArgs], {
cwd: this.directory,
});
console.info(`Finished installing ledger plugins`);
}
return { totalInstalled: pnpmAddArgs.length, previouslyInstalled: 0 };
}
removeExtraPendingConnectorInstalls() {
for (const pluginInstall of this.pendingPluginInstallPayloads.values()) {
if (
// if this install is a connector (it could be a gatsby plugin)
this.isConnector(pluginInstall) &&
// and if there are no dynamic connectors
this.dynamicConnectorIds.size === 0) {
// don't install it - installing is slow and we want to avoid it if we can.
// we need to install all connectors if ONE is dynamic.
// We can't install only dynamic connectors because we need to clear and re-run schema customization for dynamic connectors to work and if we clear, we then need to re-run all schema customization or we will lose non-dynamic connector schemas
// so here we only delete pending installs if we have 0 dynamic connectors
// if we have more than that we need to install all connectors
this.pendingPluginInstallPayloads.delete(pluginInstall.id);
}
}
}
removeUnneededGatsbyPlugins() {
const pendingPluginsArray = Array.from(this.pendingPluginInstallPayloads.values());
const hasAnyGatsbyPlugin = pendingPluginsArray.some((plugin) => this.isGatsbyPlugin(plugin));
if (hasAnyGatsbyPlugin) {
const hasSourcePlugins = pendingPluginsArray.some((plugin) => this.isGatsbySourcePlugin(plugin));
const gatsbyPluginImage = pendingPluginsArray.find((plugin) => this.isGatsbyPluginImage(plugin));
// if there are no source plugins to install, do not install plugin image
if (gatsbyPluginImage && !hasSourcePlugins) {
this.pendingPluginInstallPayloads.delete(gatsbyPluginImage.id);
}
}
}
getPnpmAddArguments() {
const pendingInstallArgs = Array.from(this.pendingPluginInstallPayloads.values())
.map((plugin) => this.getPluginPnpmInstallString(plugin))
.filter((p) => Boolean(p));
const thisGraphQLPath = path_1.default.dirname(require.resolve(`graphql/package.json`));
return [...pendingInstallArgs, `link:${thisGraphQLPath}`].filter((arg) => arg !== "" && arg !== undefined);
}
getPnpmAddArgumentsForWriter() {
const pendingAddArgs = Array.from(this.pendingPluginInstallPayloads.values())
.map((plugin) => plugin.pluginFilepath)
.filter((p) => Boolean(p));
const thisGraphQLPath = path_1.default.dirname(require.resolve(`graphql/package.json`));
const thisContentEnginePath = path_1.default.dirname(require.resolve(`@netlify/content-engine/package.json`));
return [
...pendingAddArgs,
`link:${thisGraphQLPath}`,
`link:${thisContentEnginePath}`,
];
}
getConnectorInstallUrlFromSitePlugin(plugin) {
if (!this.sitePlugin) {
throw new Error(`Couldn't find engine package.json details to use for installing ${plugin.name} from ledger.`);
}
let installedDep = this.findDependency(this.sitePlugin.packageJson, plugin.name);
if (!installedDep && plugin.name.endsWith(`-connector`)) {
// if the plugin name ends with -connector it might actually be something like `cms-connector-connector` because
// the SDK annoyingly adds -connector to the end of published package names. While Connect may add the plugin to
// package.json and the internal config without this additional string
// so look it up without this string as a fallback to make sure we still install it if we can.
installedDep = this.findDependency(this.sitePlugin.packageJson, plugin.name.substring(0, plugin.name.length - `-connector`.length));
}
if (!installedDep) {
throw new Error(`Couldn't find ${plugin.name} install url.`);
}
return installedDep.version.startsWith(`http`) ||
installedDep.version.startsWith(`file:`) ||
installedDep.version.startsWith(`link:`)
? // if it's a url or file install via the version
installedDep.version
: // otherwise we can't actually do a url install - try installing via the package name
installedDep.name;
}
getPluginPnpmInstallString(plugin) {
const pluginAsSiteDep = this.findDependency(this.sitePlugin?.packageJson, plugin.name);
switch (true) {
case this.isLocalPluginOnDisk(plugin):
return `link:${plugin.pluginFilepath}`;
case this.isUrlInstallablePlugin(plugin): {
return this.getConnectorInstallUrlFromSitePlugin(plugin);
}
case this.isGatsbyPlugin(plugin): {
this.sawAtleastOneGatsbyPlugin = true;
if (pluginAsSiteDep) {
return `${pluginAsSiteDep.name}@${pluginAsSiteDep.version}`;
}
else {
return plugin.name;
}
}
}
return false;
}
findDependency(packageJson, desiredPackageName) {
if (!packageJson) {
return false;
}
const foundDep = [
...(packageJson?.dependencies || []),
...(packageJson?.devDependencies || []),
...(packageJson?.peerDependencies || []),
].find((dep) => dep.name === desiredPackageName);
return foundDep || false;
}
isUrlInstallablePlugin(plugin) {
return Boolean(this.findDependency(plugin.packageJson, `@netlify/sdk`));
}
isLocalPluginOnDisk(plugin) {
return (path_1.default.isAbsolute(plugin.pluginFilepath) &&
!plugin.pluginFilepath.includes(`node_modules`) &&
(0, fs_extra_1.existsSync)(plugin.pluginFilepath));
}
isInstallablePluginAction(action) {
return (this.isSitePluginAction(action) &&
(this.isGatsbyPlugin(action.payload) || this.isConnector(action.payload)));
}
isSitePluginAction(action) {
return (action.type === `CREATE_NODE` &&
action.payload?.internal?.owner === `internal-data-bridge` &&
action.payload?.internal?.type === `SitePlugin`);
}
isGatsbySourcePlugin(plugin) {
return plugin.name.startsWith(`gatsby-source-`);
}
isGatsbyPluginImage(plugin) {
return plugin.name === `gatsby-plugin-image`;
}
isGatsbyPlugin(plugin) {
return (this.isGatsbySourcePlugin(plugin) ||
// need to install plugin image for some source plugins to work
this.isGatsbyPluginImage(plugin));
}
isDefaultSitePlugin(plugin) {
return plugin?.name === `default-site-plugin`;
}
isConnector(plugin) {
return (
// not a Gatsby plugin
!this.isGatsbyPlugin(plugin) &&
plugin.name !== `internal-data-bridge` &&
!this.isDefaultSitePlugin(plugin) &&
// and the plugin implements createSchemaCustomization
plugin.nodeAPIs.includes(`createSchemaCustomization`));
}
}
exports.LedgerDependencyManager = LedgerDependencyManager;
//# sourceMappingURL=ledger-dependency-manager.js.map
;