UNPKG

@netlify/content-engine

Version:
295 lines 13.7 kB
"use strict"; 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