|
| 1 | +// eslint-env node |
| 2 | +/* eslint-disable n/prefer-global/process */ |
| 3 | +import { promises as fs } from 'fs' |
| 4 | +import path from 'path' |
| 5 | + |
| 6 | +import sanityClient from '@sanity/client' |
| 7 | +import { uuid } from '@sanity/uuid' |
| 8 | + |
| 9 | +// when testing this script locally, add a path in your .env for GITHUB_WORKSPACE or pass it in |
| 10 | +// e.g. GITHUB_WORKSPACE="$(pwd)" npx tsx bin/sync_plugins_to_cms.js |
| 11 | + |
| 12 | +/** |
| 13 | + * @typedef { import("../types/plugins").SanityBuildPluginEntity } SanityBuildPluginEntity |
| 14 | + * @typedef { import("@sanity/client").SanityClient } SanityClient |
| 15 | + * @typedef { import("@sanity/client").Transaction } Transaction |
| 16 | + * @typedef { import("@sanity/client").Patch } Patch |
| 17 | + * @typedef { import("../types/plugins").BuildPluginEntity } BuildPluginEntity |
| 18 | + */ |
| 19 | + |
| 20 | +import { getPluginDiffsForSanity, getSanityPluginLookup } from './utils.js' |
| 21 | + |
| 22 | +if (process.env.NODE_ENV === 'development') { |
| 23 | + // Using dotenv for local development. |
| 24 | + console.log('running in development mode') |
| 25 | + |
| 26 | + const dotenv = await import('dotenv') |
| 27 | + dotenv.config() |
| 28 | +} |
| 29 | + |
| 30 | +const { GITHUB_WORKSPACE, SANITY_API_TOKEN, SANITY_PROJECT_ID, SANITY_DATASET } = process.env |
| 31 | +const [apiVersion] = new Date().toISOString().split('T') |
| 32 | + |
| 33 | +const config = { |
| 34 | + projectId: SANITY_PROJECT_ID, |
| 35 | + dataset: SANITY_DATASET, |
| 36 | + apiVersion, |
| 37 | + token: SANITY_API_TOKEN, |
| 38 | + // make sure we have the freshest data when doing the diff with plugins.json |
| 39 | + useCdn: false, |
| 40 | +} |
| 41 | + |
| 42 | +/** |
| 43 | + * Creates a transaction containing updates to plugins for the CMS |
| 44 | + * |
| 45 | + * @param {Transaction} transaction |
| 46 | + * @param {Patch} patch |
| 47 | + * @param {BuildPluginEntity[]} diffs |
| 48 | + * @returns |
| 49 | + */ |
| 50 | +const createUpdates = (transaction, patch, diffs) => |
| 51 | + diffs.reduce((tx, plugin) => { |
| 52 | + const { _id, ...changes } = plugin |
| 53 | + const fieldUpdates = {} |
| 54 | + const fieldRemovals = [] |
| 55 | + |
| 56 | + for (const [key, value] of Object.entries(changes)) { |
| 57 | + // any property that is null needs to be unset instead of being set to null |
| 58 | + if (value === null) { |
| 59 | + fieldRemovals.push(key) |
| 60 | + } else { |
| 61 | + fieldUpdates[key] = value |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + const update = patch(_id).set(fieldUpdates) |
| 66 | + |
| 67 | + if (fieldRemovals.length !== 0) { |
| 68 | + update.unset(fieldRemovals) |
| 69 | + } |
| 70 | + |
| 71 | + tx.patch(update) |
| 72 | + |
| 73 | + return tx |
| 74 | + }, transaction) |
| 75 | + |
| 76 | +/** |
| 77 | + * @type {SanityClient} |
| 78 | + */ |
| 79 | +const client = sanityClient(config) |
| 80 | + |
| 81 | +// These are the only fields to synch for the moment. |
| 82 | +const query = `*[_type == "buildPlugin"] { |
| 83 | + _id, |
| 84 | + packageName, |
| 85 | + version, |
| 86 | + compatibility[] |
| 87 | +}` |
| 88 | + |
| 89 | +// TODO: Add a retry mechanism to handle network errors |
| 90 | +try { |
| 91 | + const pluginsFilePath = path.join(GITHUB_WORKSPACE, '/site/plugins.json') |
| 92 | + const fileContents = await fs.readFile(pluginsFilePath) |
| 93 | + const plugins = JSON.parse(fileContents).map((plugin) => { |
| 94 | + // Ensure if a compatibility field exists, that it has all the necessary fields to sync with Sanity |
| 95 | + if (plugin.compatibility) { |
| 96 | + // eslint-disable-next-line no-param-reassign |
| 97 | + plugin.compatibility = plugin.compatibility.map((compatibilityItem) => { |
| 98 | + const updatedCompatibilityItem = { |
| 99 | + // A _key property is required by Sanity so each array item can be identified in a collaborative way |
| 100 | + // See https://www.sanity.io/docs/array-type#92296c6c45ea |
| 101 | + _key: uuid(), |
| 102 | + ...compatibilityItem, |
| 103 | + } |
| 104 | + |
| 105 | + return updatedCompatibilityItem |
| 106 | + }) |
| 107 | + } |
| 108 | + |
| 109 | + return plugin |
| 110 | + }) |
| 111 | + |
| 112 | + console.info('Detecting plugin changes...') |
| 113 | + |
| 114 | + /** |
| 115 | + * @type {SanityBuildPluginEntity[]} |
| 116 | + */ |
| 117 | + const sanityBuildPlugins = await client.fetch(query, {}) |
| 118 | + const sanityPluginLookup = await getSanityPluginLookup(sanityBuildPlugins) |
| 119 | + const pluginDiffs = getPluginDiffsForSanity(sanityPluginLookup, plugins) |
| 120 | + |
| 121 | + if (pluginDiffs.length === 0) { |
| 122 | + console.info('No plugin changes found.') |
| 123 | + } else { |
| 124 | + console.info(`Found ${pluginDiffs.length} plugins with changes`) |
| 125 | + console.info('Updating plugins...') |
| 126 | + |
| 127 | + const transaction = createUpdates(client.transaction(), client.patch, pluginDiffs) |
| 128 | + |
| 129 | + client.mutate(transaction) |
| 130 | + console.info('Plugins were updated in the CMS.') |
| 131 | + } |
| 132 | +} catch (error) { |
| 133 | + console.error(error) |
| 134 | + throw new Error('Unable to synchronize plugins to the CMS') |
| 135 | +} |
| 136 | + |
| 137 | +/* eslint-enable n/prefer-global/process */ |
0 commit comments