Skip to content

Commit 0f5f7e4

Browse files
gatsbybotLekoArts
andauthored
fix(gatsby-cli): Re-Add plugin-add functionality (#34482) (#34510)
(cherry picked from commit 618b32b) Co-authored-by: Lennart <[email protected]>
1 parent 0d29e95 commit 0f5f7e4

File tree

8 files changed

+665
-264
lines changed

8 files changed

+665
-264
lines changed

packages/create-gatsby/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,5 @@ Open another terminal window and go to a folder where you can easily delete the
6262
cd <path-to-playground>
6363
6464
# Run the create-gatsby script
65-
node <some-path/packages/create-gatsby/cli.js
65+
node <some-path>/packages/create-gatsby/cli.js
6666
```

packages/gatsby-cli/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
},
1212
"dependencies": {
1313
"@babel/code-frame": "^7.14.0",
14+
"@babel/core": "^7.15.5",
15+
"@babel/generator": "^7.16.8",
16+
"@babel/helper-plugin-utils": "^7.16.7",
1417
"@babel/runtime": "^7.15.4",
18+
"@babel/template": "^7.16.7",
19+
"@babel/types": "^7.16.8",
1520
"@types/common-tags": "^1.8.1",
1621
"better-opn": "^2.1.1",
1722
"boxen": "^5.1.2",
@@ -52,7 +57,6 @@
5257
},
5358
"devDependencies": {
5459
"@babel/cli": "^7.15.4",
55-
"@babel/core": "^7.15.5",
5660
"@rollup/plugin-babel": "^5.3.0",
5761
"@rollup/plugin-commonjs": "^17.1.0",
5862
"@rollup/plugin-json": "^4.1.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import * as fs from "fs-extra"
2+
import execa from "execa"
3+
import _ from "lodash"
4+
import {
5+
readConfigFile,
6+
lock,
7+
getConfigPath,
8+
getConfigStore,
9+
} from "gatsby-core-utils"
10+
import { transform } from "@babel/core"
11+
import { BabelPluginAddPluginsToGatsbyConfig } from "./plugin-babel-utils"
12+
13+
const addPluginToConfig = (
14+
src: string,
15+
{
16+
name,
17+
options,
18+
key,
19+
}: {
20+
name: string
21+
options: Record<string, unknown> | undefined
22+
key: string
23+
}
24+
): string => {
25+
const addPlugins = new BabelPluginAddPluginsToGatsbyConfig({
26+
pluginOrThemeName: name,
27+
options,
28+
shouldAdd: true,
29+
key,
30+
})
31+
32+
// @ts-ignore - fix me
33+
const { code } = transform(src, {
34+
// @ts-ignore - fix me
35+
plugins: [addPlugins.plugin],
36+
configFile: false,
37+
})
38+
39+
return code
40+
}
41+
42+
interface IGatsbyPluginCreateInput {
43+
root: string
44+
name: string
45+
options: Record<string, unknown> | undefined
46+
key: string
47+
}
48+
49+
export const GatsbyPluginCreate = async ({
50+
root,
51+
name,
52+
options,
53+
key,
54+
}: IGatsbyPluginCreateInput): Promise<void> => {
55+
const release = await lock(`gatsby-config.js`)
56+
const configSrc = await readConfigFile(root)
57+
58+
const code = addPluginToConfig(configSrc, { name, options, key })
59+
60+
await fs.writeFile(getConfigPath(root), code)
61+
release()
62+
}
63+
64+
const packageMangerConfigKey = `cli.packageManager`
65+
const PACKAGE_MANAGER = getConfigStore().get(packageMangerConfigKey) || `yarn`
66+
67+
const getPackageNames = (
68+
packages: Array<{ name: string; version: string }>
69+
): Array<string> => packages.map(n => `${n.name}@${n.version}`)
70+
71+
const generateClientCommands = ({
72+
packageManager,
73+
depType,
74+
packageNames,
75+
}: {
76+
packageManager: string
77+
depType: string
78+
packageNames: Array<string>
79+
}): Array<string> | undefined => {
80+
const commands: Array<string> = []
81+
if (packageManager === `yarn`) {
82+
commands.push(`add`)
83+
// Needed for Yarn Workspaces and is a no-opt elsewhere.
84+
commands.push(`-W`)
85+
if (depType === `development`) {
86+
commands.push(`--dev`)
87+
}
88+
89+
return commands.concat(packageNames)
90+
} else if (packageManager === `npm`) {
91+
commands.push(`install`)
92+
if (depType === `development`) {
93+
commands.push(`--save-dev`)
94+
}
95+
return commands.concat(packageNames)
96+
}
97+
98+
return undefined
99+
}
100+
101+
let installs: Array<{
102+
outsideResolve: any
103+
outsideReject: any
104+
resource: any
105+
}> = []
106+
const executeInstalls = async (root: string): Promise<void> => {
107+
// @ts-ignore - fix me
108+
const types = _.groupBy(installs, c => c.resource.dependencyType)
109+
110+
// Grab the key of the first install & delete off installs these packages
111+
// then run intall
112+
// when done, check again & call executeInstalls again.
113+
// @ts-ignore - fix me
114+
const depType = installs[0].resource.dependencyType
115+
const packagesToInstall = types[depType]
116+
installs = installs.filter(
117+
// @ts-ignore - fix me
118+
i => !packagesToInstall.some(p => i.resource.name === p.resource.name)
119+
)
120+
121+
// @ts-ignore - fix me
122+
const pkgs = packagesToInstall.map(p => p.resource)
123+
const packageNames = getPackageNames(pkgs)
124+
125+
const commands = generateClientCommands({
126+
packageNames,
127+
depType,
128+
packageManager: PACKAGE_MANAGER,
129+
})
130+
131+
const release = await lock(`package.json`)
132+
try {
133+
await execa(PACKAGE_MANAGER, commands, {
134+
cwd: root,
135+
})
136+
} catch (e) {
137+
// A package failed so call the rejects
138+
return packagesToInstall.forEach(p => {
139+
// @ts-ignore - fix me
140+
p.outsideReject(
141+
JSON.stringify({
142+
message: e.shortMessage,
143+
installationError: `Could not install package`,
144+
})
145+
)
146+
})
147+
}
148+
release()
149+
150+
// @ts-ignore - fix me
151+
packagesToInstall.forEach(p => p.outsideResolve())
152+
153+
// Run again if there's still more installs.
154+
if (installs.length > 0) {
155+
executeInstalls(root)
156+
}
157+
158+
return undefined
159+
}
160+
161+
const debouncedExecute = _.debounce(executeInstalls, 25)
162+
163+
interface IPackageCreateInput {
164+
root: string
165+
name: string
166+
}
167+
168+
const createInstall = async ({
169+
root,
170+
name,
171+
}: IPackageCreateInput): Promise<unknown> => {
172+
let outsideResolve
173+
let outsideReject
174+
const promise = new Promise((resolve, reject) => {
175+
outsideResolve = resolve
176+
outsideReject = reject
177+
})
178+
installs.push({
179+
outsideResolve,
180+
outsideReject,
181+
resource: name,
182+
})
183+
184+
debouncedExecute(root)
185+
return promise
186+
}
187+
188+
export const NPMPackageCreate = async ({
189+
root,
190+
name,
191+
}: IPackageCreateInput): Promise<void> => {
192+
await createInstall({ root, name })
193+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import reporter from "../reporter"
2+
import { GatsbyPluginCreate, NPMPackageCreate } from "./plugin-add-utils"
3+
4+
const normalizePluginName = (plugin: string): string => {
5+
if (plugin.startsWith(`gatsby-`)) {
6+
return plugin
7+
}
8+
if (
9+
plugin.startsWith(`source-`) ||
10+
plugin.startsWith(`transformer-`) ||
11+
plugin.startsWith(`plugin-`)
12+
) {
13+
return `gatsby-${plugin}`
14+
}
15+
return `gatsby-plugin-${plugin}`
16+
}
17+
18+
async function installPluginPackage(
19+
plugin: string,
20+
root: string
21+
): Promise<void> {
22+
const installTimer = reporter.activityTimer(`Installing ${plugin}`)
23+
24+
installTimer.start()
25+
reporter.info(`Installing ${plugin}`)
26+
try {
27+
await NPMPackageCreate({ root, name: plugin })
28+
reporter.info(`Installed NPM package ${plugin}`)
29+
} catch (err) {
30+
reporter.error(JSON.parse(err)?.message)
31+
installTimer.setStatus(`FAILED`)
32+
}
33+
installTimer.end()
34+
}
35+
36+
async function installPluginConfig(
37+
plugin: string,
38+
options: Record<string, unknown> | undefined,
39+
root: string
40+
): Promise<void> {
41+
// Plugins can optionally include a key, to allow duplicates
42+
const [pluginName, pluginKey] = plugin.split(`:`)
43+
44+
const installTimer = reporter.activityTimer(
45+
`Adding ${pluginName} ${pluginKey ? `(${pluginKey}) ` : ``}to gatsby-config`
46+
)
47+
48+
installTimer.start()
49+
reporter.info(`Adding ${pluginName}`)
50+
try {
51+
await GatsbyPluginCreate({
52+
root,
53+
name: pluginName,
54+
options,
55+
key: pluginKey,
56+
})
57+
reporter.info(`Installed ${pluginName || pluginKey} in gatsby-config.js`)
58+
} catch (err) {
59+
reporter.error(JSON.parse(err)?.message)
60+
installTimer.setStatus(`FAILED`)
61+
}
62+
installTimer.end()
63+
}
64+
65+
export async function addPlugins(
66+
plugins: Array<string>,
67+
pluginOptions: Record<string, Record<string, unknown>>,
68+
directory: string,
69+
packages: Array<string> = []
70+
): Promise<void> {
71+
if (!plugins?.length) {
72+
reporter.error(`Please specify a plugin to install`)
73+
return
74+
}
75+
76+
const pluginList = plugins.map(normalizePluginName)
77+
78+
await Promise.all(
79+
packages.map(plugin => installPluginPackage(plugin, directory))
80+
)
81+
await Promise.all(
82+
pluginList.map(plugin =>
83+
installPluginConfig(plugin, pluginOptions[plugin], directory)
84+
)
85+
)
86+
}

0 commit comments

Comments
 (0)