Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby-cli): Re-Add plugin-add functionality #34482

Merged
merged 4 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/create-gatsby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,5 @@ Open another terminal window and go to a folder where you can easily delete the
cd <path-to-playground>

# Run the create-gatsby script
node <some-path/packages/create-gatsby/cli.js
node <some-path>/packages/create-gatsby/cli.js
```
6 changes: 5 additions & 1 deletion packages/gatsby-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
},
"dependencies": {
"@babel/code-frame": "^7.14.0",
"@babel/core": "^7.15.5",
"@babel/generator": "^7.16.8",
"@babel/helper-plugin-utils": "^7.16.7",
"@babel/runtime": "^7.15.4",
"@babel/template": "^7.16.7",
"@babel/types": "^7.16.8",
"@types/common-tags": "^1.8.1",
"better-opn": "^2.1.1",
"boxen": "^5.1.2",
Expand Down Expand Up @@ -52,7 +57,6 @@
},
"devDependencies": {
"@babel/cli": "^7.15.4",
"@babel/core": "^7.15.5",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-json": "^4.1.0",
Expand Down
193 changes: 193 additions & 0 deletions packages/gatsby-cli/src/handlers/plugin-add-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import * as fs from "fs-extra"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import execa from "execa"
import _ from "lodash"
import {
readConfigFile,
lock,
getConfigPath,
getConfigStore,
} from "gatsby-core-utils"
import { transform } from "@babel/core"
import { BabelPluginAddPluginsToGatsbyConfig } from "./plugin-babel-utils"

const addPluginToConfig = (
src: string,
{
name,
options,
key,
}: {
name: string
options: Record<string, unknown> | undefined
key: string
}
): string => {
const addPlugins = new BabelPluginAddPluginsToGatsbyConfig({
pluginOrThemeName: name,
options,
shouldAdd: true,
key,
})

// @ts-ignore - fix me
const { code } = transform(src, {
// @ts-ignore - fix me
plugins: [addPlugins.plugin],
configFile: false,
})

return code
}

interface IGatsbyPluginCreateInput {
root: string
name: string
options: Record<string, unknown> | undefined
key: string
}

export const GatsbyPluginCreate = async ({
root,
name,
options,
key,
}: IGatsbyPluginCreateInput): Promise<void> => {
const release = await lock(`gatsby-config.js`)
const configSrc = await readConfigFile(root)

const code = addPluginToConfig(configSrc, { name, options, key })

await fs.writeFile(getConfigPath(root), code)
release()
}

const packageMangerConfigKey = `cli.packageManager`
const PACKAGE_MANAGER = getConfigStore().get(packageMangerConfigKey) || `yarn`

const getPackageNames = (
packages: Array<{ name: string; version: string }>
): Array<string> => packages.map(n => `${n.name}@${n.version}`)

const generateClientCommands = ({
packageManager,
depType,
packageNames,
}: {
packageManager: string
depType: string
packageNames: Array<string>
}): Array<string> | undefined => {
const commands: Array<string> = []
if (packageManager === `yarn`) {
commands.push(`add`)
// Needed for Yarn Workspaces and is a no-opt elsewhere.
commands.push(`-W`)
if (depType === `development`) {
commands.push(`--dev`)
}

return commands.concat(packageNames)
} else if (packageManager === `npm`) {
commands.push(`install`)
if (depType === `development`) {
commands.push(`--save-dev`)
}
return commands.concat(packageNames)
}

return undefined
}

let installs: Array<{
outsideResolve: any
outsideReject: any
resource: any
}> = []
const executeInstalls = async (root: string): Promise<void> => {
// @ts-ignore - fix me
const types = _.groupBy(installs, c => c.resource.dependencyType)

// Grab the key of the first install & delete off installs these packages
// then run intall
// when done, check again & call executeInstalls again.
// @ts-ignore - fix me
const depType = installs[0].resource.dependencyType
const packagesToInstall = types[depType]
installs = installs.filter(
// @ts-ignore - fix me
i => !packagesToInstall.some(p => i.resource.name === p.resource.name)
)

// @ts-ignore - fix me
const pkgs = packagesToInstall.map(p => p.resource)
const packageNames = getPackageNames(pkgs)

const commands = generateClientCommands({
packageNames,
depType,
packageManager: PACKAGE_MANAGER,
})

const release = await lock(`package.json`)
try {
await execa(PACKAGE_MANAGER, commands, {
cwd: root,
})
} catch (e) {
// A package failed so call the rejects
return packagesToInstall.forEach(p => {
// @ts-ignore - fix me
p.outsideReject(
JSON.stringify({
message: e.shortMessage,
installationError: `Could not install package`,
})
)
})
}
release()

// @ts-ignore - fix me
packagesToInstall.forEach(p => p.outsideResolve())

// Run again if there's still more installs.
if (installs.length > 0) {
executeInstalls(root)
}

return undefined
}

const debouncedExecute = _.debounce(executeInstalls, 25)

interface IPackageCreateInput {
root: string
name: string
}

const createInstall = async ({
root,
name,
}: IPackageCreateInput): Promise<unknown> => {
let outsideResolve
let outsideReject
const promise = new Promise((resolve, reject) => {
outsideResolve = resolve
outsideReject = reject
})
installs.push({
outsideResolve,
outsideReject,
resource: name,
})

debouncedExecute(root)
return promise
}

export const NPMPackageCreate = async ({
root,
name,
}: IPackageCreateInput): Promise<void> => {
await createInstall({ root, name })
}
86 changes: 86 additions & 0 deletions packages/gatsby-cli/src/handlers/plugin-add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import reporter from "../reporter"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import { GatsbyPluginCreate, NPMPackageCreate } from "./plugin-add-utils"

const normalizePluginName = (plugin: string): string => {
if (plugin.startsWith(`gatsby-`)) {
return plugin
}
if (
plugin.startsWith(`source-`) ||
plugin.startsWith(`transformer-`) ||
plugin.startsWith(`plugin-`)
) {
return `gatsby-${plugin}`
}
return `gatsby-plugin-${plugin}`
}

async function installPluginPackage(
plugin: string,
root: string
): Promise<void> {
const installTimer = reporter.activityTimer(`Installing ${plugin}`)

installTimer.start()
reporter.info(`Installing ${plugin}`)
try {
await NPMPackageCreate({ root, name: plugin })
reporter.info(`Installed NPM package ${plugin}`)
} catch (err) {
reporter.error(JSON.parse(err)?.message)
installTimer.setStatus(`FAILED`)
}
installTimer.end()
}

async function installPluginConfig(
plugin: string,
options: Record<string, unknown> | undefined,
root: string
): Promise<void> {
// Plugins can optionally include a key, to allow duplicates
const [pluginName, pluginKey] = plugin.split(`:`)

const installTimer = reporter.activityTimer(
`Adding ${pluginName} ${pluginKey ? `(${pluginKey}) ` : ``}to gatsby-config`
)

installTimer.start()
reporter.info(`Adding ${pluginName}`)
try {
await GatsbyPluginCreate({
root,
name: pluginName,
options,
key: pluginKey,
})
reporter.info(`Installed ${pluginName || pluginKey} in gatsby-config.js`)
} catch (err) {
reporter.error(JSON.parse(err)?.message)
installTimer.setStatus(`FAILED`)
}
installTimer.end()
}

export async function addPlugins(
plugins: Array<string>,
pluginOptions: Record<string, Record<string, unknown>>,
directory: string,
packages: Array<string> = []
): Promise<void> {
if (!plugins?.length) {
reporter.error(`Please specify a plugin to install`)
return
}

const pluginList = plugins.map(normalizePluginName)

await Promise.all(
packages.map(plugin => installPluginPackage(plugin, directory))
)
await Promise.all(
pluginList.map(plugin =>
installPluginConfig(plugin, pluginOptions[plugin], directory)
)
)
}
Loading