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

feat(gatsby-core-utils,gatsby-cli): Allow write to gatsby-config.ts #35074

Merged
merged 16 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from 11 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
1 change: 1 addition & 0 deletions packages/gatsby-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@babel/core": "^7.15.5",
"@babel/generator": "^7.16.8",
"@babel/helper-plugin-utils": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@babel/runtime": "^7.15.4",
"@babel/template": "^7.16.7",
"@babel/types": "^7.16.8",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For anyone reading this (or my future self), I took a look at making the quote type of the output strings in the transform consistent (e.g. all backticks).

The only obstacle is in the buildPluginNode function we JSON.stringify the options object, and if we try to make object keys backticks then the transform fails since that's invalid JSON (and we can't expect keys to only be alphabetic).

Anyhow not a huge deal, but wanted to leave this for posterity. Will leave this out of this PR.


exports[`addPlugins gatsby-config.js should not write with no plugins 1`] = `
"module.exports = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`,
},
plugins: [],
}
"
`;

exports[`addPlugins gatsby-config.js should write a plugin with options 1`] = `
"module.exports = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`
},
plugins: [{
resolve: 'gatsby-plugin-hello',
options: {
\\"greet\\": true
}
}]
};"
`;

exports[`addPlugins gatsby-config.js should write a single plugin 1`] = `
"module.exports = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`
},
plugins: [\\"gatsby-plugin-hello\\"]
};"
`;

exports[`addPlugins gatsby-config.js should write multiple plugins 1`] = `
"module.exports = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`
},
plugins: [\\"gatsby-plugin-hello\\", \\"gatsby-plugin-world\\"]
};"
`;

exports[`addPlugins gatsby-config.ts should not write with no plugins 1`] = `
"import type { GatsbyConfig } from \\"gatsby\\"

const config: GatsbyConfig = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`,
},
plugins: [],
}

export default config
"
`;

exports[`addPlugins gatsby-config.ts should write a plugin with options 1`] = `
"import type { GatsbyConfig } from 'gatsby;'

const config: GatsbyConfig = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`
},
plugins: [{
resolve: 'gatsby-plugin-hello',
options: {
\\"greet\\": true
}
}]
};

export default config;
"
`;

exports[`addPlugins gatsby-config.ts should write a single plugin 1`] = `
"import type { GatsbyConfig } from 'gatsby;'

const config: GatsbyConfig = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`
},
plugins: [\\"gatsby-plugin-hello\\"]
};

export default config;
"
`;

exports[`addPlugins gatsby-config.ts should write multiple plugins 1`] = `
"import type { GatsbyConfig } from 'gatsby;'

const config: GatsbyConfig = {
siteMetadata: {
siteUrl: \`https://www.yourdomain.tld\`
},
plugins: [\\"gatsby-plugin-hello\\", \\"gatsby-plugin-world\\"]
};

export default config;
"
`;
121 changes: 121 additions & 0 deletions packages/gatsby-cli/src/__tests__/handlers/plugin-add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { copyFile, readFile, rm } from "fs/promises"
tyhopp marked this conversation as resolved.
Show resolved Hide resolved
import { ensureDir } from "fs-extra"
import { join } from "path"
import { addPlugins } from "../../handlers/plugin-add"

/**
* Copy files from minimal starters instead of testing against static gatsby-configs
* in fixtues so that these break if we change the starter configs in a breaking way.
*
* Not using `jest.each` since I find that much harder to read and debug.
* @see {@link https://jestjs.io/docs/api#testeachtablename-fn-timeout}
*/

const root = join(__dirname, `../../../../..`)
const fixtures = join(__dirname, `../fixtures`)
const config = {
js: {
starter: `${root}/starters/gatsby-starter-minimal/gatsby-config.js`,
fixture: `${fixtures}/gatsby-config.js`,
},
ts: {
starter: `${root}/starters/gatsby-starter-minimal-ts/gatsby-config.ts`,
fixture: `${fixtures}/gatsby-config.ts`,
},
}
const plugin = {
hello: `gatsby-plugin-hello`,
world: `gatsby-plugin-world`,
}

describe(`addPlugins`, () => {
beforeAll(async () => {
await ensureDir(fixtures)
})

describe(`gatsby-config.js`, () => {
beforeEach(async () => {
await copyFile(config.js.starter, config.js.fixture)
})

afterEach(async () => {
await rm(config.js.fixture)
})

it(`should not write with no plugins`, async () => {
await addPlugins([], {}, fixtures, [])
const gatsbyConfig = (await readFile(config.js.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})

it(`should write a single plugin`, async () => {
await addPlugins([plugin.hello], {}, fixtures, [])
const gatsbyConfig = (await readFile(config.js.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})

it(`should write multiple plugins`, async () => {
await addPlugins([plugin.hello, plugin.world], {}, fixtures, [])
const gatsbyConfig = (await readFile(config.js.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})

it(`should write a plugin with options`, async () => {
await addPlugins(
[plugin.hello],
{
[plugin.hello]: {
greet: true,
},
},
fixtures,
[]
)
const gatsbyConfig = (await readFile(config.js.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})
})

describe(`gatsby-config.ts`, () => {
beforeEach(async () => {
await copyFile(config.ts.starter, config.ts.fixture)
})

afterEach(async () => {
await rm(config.ts.fixture)
})

it(`should not write with no plugins`, async () => {
await addPlugins([], {}, fixtures, [])
const gatsbyConfig = (await readFile(config.ts.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})

it(`should write a single plugin`, async () => {
await addPlugins([plugin.hello], {}, fixtures, [])
const gatsbyConfig = (await readFile(config.ts.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})

it(`should write multiple plugins`, async () => {
await addPlugins([plugin.hello, plugin.world], {}, fixtures, [])
const gatsbyConfig = (await readFile(config.ts.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})

it(`should write a plugin with options`, async () => {
await addPlugins(
[plugin.hello],
{
[plugin.hello]: {
greet: true,
},
},
fixtures,
[]
)
const gatsbyConfig = (await readFile(config.ts.fixture)).toString()
expect(gatsbyConfig).toMatchSnapshot()
})
})
})
60 changes: 43 additions & 17 deletions packages/gatsby-cli/src/handlers/plugin-add-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import {
getConfigPath,
getConfigStore,
} from "gatsby-core-utils"
import { transform } from "@babel/core"
import { BabelPluginAddPluginsToGatsbyConfig } from "./plugin-babel-utils"
import { transform, TransformOptions } from "@babel/core"
import BabelPluginAddPluginsToGatsbyConfig from "./plugin-babel-utils"

const addPluginToConfig = (
src: string,
srcPath: string,
{
name,
options,
Expand All @@ -22,19 +23,41 @@ const addPluginToConfig = (
key: string
}
): string => {
const addPlugins = new BabelPluginAddPluginsToGatsbyConfig({
pluginOrThemeName: name,
options,
shouldAdd: true,
key,
})
let code

// @ts-ignore - fix me
const { code } = transform(src, {
// @ts-ignore - fix me
plugins: [addPlugins.plugin],
configFile: false,
})
try {
const transformOptions: TransformOptions = {
plugins: [
[
BabelPluginAddPluginsToGatsbyConfig,
{
pluginOrThemeName: name,
options,
key,
},
],
],
filename: srcPath,
configFile: false,
}

// Use the Babel TS preset if we're operating on `gatsby-config.ts`
if (srcPath.endsWith(`ts`)) {
transformOptions.presets = [`@babel/preset-typescript`]
}

code = transform(src, transformOptions)?.code

// Add back stripped type import, do light formatting, remove added empty module export.
// Use semicolon since Babel does that anyway, and we might as well be consistent.
if (srcPath.endsWith(`ts`)) {
code = `import type { GatsbyConfig } from 'gatsby;'\n\n${code}`
tyhopp marked this conversation as resolved.
Show resolved Hide resolved
code = code.replace(`export {};`, ``)
LekoArts marked this conversation as resolved.
Show resolved Hide resolved
code = code.replace(`export default config;`, `\nexport default config;`)
}
} catch (error) {
console.error(`Failed to transform gatsby config`, error)
}

return code
}
Expand All @@ -53,10 +76,13 @@ export const GatsbyPluginCreate = async ({
key,
}: IGatsbyPluginCreateInput): Promise<void> => {
const release = await lock(`gatsby-config.js`)
const configSrcPath = getConfigPath(root)
const configSrc = await readConfigFile(root)

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

const code = addPluginToConfig(configSrc, configSrcPath, {
name,
options,
key,
})
await fs.writeFile(getConfigPath(root), code)
release()
}
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby-cli/src/handlers/plugin-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async function installPluginConfig(
options,
key: pluginKey,
})
reporter.info(`Installed ${pluginName || pluginKey} in gatsby-config.js`)
reporter.info(`Installed ${pluginName || pluginKey} in gatsby-config`)
} catch (err) {
reporter.error(JSON.parse(err)?.message)
installTimer.setStatus(`FAILED`)
Expand Down
Loading