Skip to content

Commit ccbec68

Browse files
ascorbicehmicky
andauthored
fix: support gatsby base dirs outside the site root (#281)
* fix: support gatsby base dirs outside the site root * chore: changes from review Co-authored-by: ehmicky <[email protected]>
1 parent 2f4dd36 commit ccbec68

File tree

7 files changed

+83
-61
lines changed

7 files changed

+83
-61
lines changed

plugin/src/helpers/cache.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import path from 'path'
22

33
import { existsSync, readdirSync } from 'fs-extra'
44

5+
import { getGatsbyRoot } from './config'
6+
57
function getCacheDirs(publish) {
68
return [publish, normalizedCacheDir(publish)]
79
}
@@ -37,5 +39,5 @@ export async function restoreCache({ publish, utils }): Promise<void> {
3739
}
3840

3941
export function normalizedCacheDir(publish: string): string {
40-
return path.normalize(`${publish}/../.cache`)
42+
return path.join(getGatsbyRoot(publish), `.cache`)
4143
}

plugin/src/helpers/config.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ export async function spliceConfig({
3838
return fs.writeFile(fileName, out)
3939
}
4040

41-
function loadGatsbyConfig(utils): GatsbyConfig | never {
42-
const gatsbyConfigFile = path.resolve(process.cwd(), 'gatsby-config.js')
41+
function loadGatsbyConfig({ utils, publish }): GatsbyConfig | never {
42+
const gatsbyConfigFile = path.resolve(
43+
getGatsbyRoot(publish),
44+
'gatsby-config.js',
45+
)
4346
if (!existsSync(gatsbyConfigFile)) {
4447
return {}
4548
}
@@ -64,7 +67,10 @@ function hasPlugin(plugins: PluginRef[], pluginName: string): boolean {
6467

6568
export function checkGatsbyConfig({ utils, netlifyConfig }): void {
6669
// warn if gatsby-plugin-netlify is missing
67-
const gatsbyConfig = loadGatsbyConfig(utils)
70+
const gatsbyConfig = loadGatsbyConfig({
71+
utils,
72+
publish: netlifyConfig.build.publish,
73+
})
6874

6975
if (!hasPlugin(gatsbyConfig.plugins, 'gatsby-plugin-netlify')) {
7076
console.error(
@@ -152,3 +158,7 @@ export function shouldSkipFunctions(cacheDir: string): boolean {
152158

153159
return false
154160
}
161+
162+
export function getGatsbyRoot(publish: string): string {
163+
return path.resolve(path.dirname(publish))
164+
}

plugin/src/helpers/functions.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import process from 'process'
2-
31
import { NetlifyPluginConstants } from '@netlify/build'
42
import { copy, copyFile, ensureDir, existsSync, rm, writeFile } from 'fs-extra'
53
import { resolve, join, relative } from 'pathe'
64

7-
import { makeHandler } from '../templates/handlers'
5+
import { makeApiHandler, makeHandler } from '../templates/handlers'
6+
7+
import { getGatsbyRoot } from './config'
88

99
const writeFunction = async ({
1010
renderMode,
@@ -21,12 +21,22 @@ const writeFunction = async ({
2121
)
2222
}
2323

24+
const writeApiFunction = async ({ appDir, functionDir }) => {
25+
const source = makeApiHandler(appDir)
26+
// This is to ensure we're copying from the compiled js, not ts source
27+
await copy(
28+
join(__dirname, '..', '..', 'lib', 'templates', 'api'),
29+
functionDir,
30+
)
31+
await writeFile(join(functionDir, '__api.js'), source)
32+
}
33+
2434
export const writeFunctions = async ({
2535
INTERNAL_FUNCTIONS_SRC,
2636
PUBLISH_DIR,
2737
}: NetlifyPluginConstants): Promise<void> => {
28-
const siteRoot = resolve(process.cwd(), PUBLISH_DIR, '..')
29-
const functionDir = join(process.cwd(), INTERNAL_FUNCTIONS_SRC, '__api')
38+
const siteRoot = getGatsbyRoot(PUBLISH_DIR)
39+
const functionDir = resolve(INTERNAL_FUNCTIONS_SRC, '__api')
3040
const appDir = relative(functionDir, siteRoot)
3141

3242
await writeFunction({
@@ -43,17 +53,14 @@ export const writeFunctions = async ({
4353
functionsSrc: INTERNAL_FUNCTIONS_SRC,
4454
})
4555

46-
await copy(
47-
join(__dirname, '..', '..', 'lib', 'templates', 'api'),
48-
functionDir,
49-
)
56+
await writeApiFunction({ appDir, functionDir })
5057
}
5158

5259
export const deleteFunctions = async ({
5360
INTERNAL_FUNCTIONS_SRC,
5461
}: NetlifyPluginConstants): Promise<void> => {
5562
for (const func of ['__api', '__ssr', '__dsg']) {
56-
const funcDir = join(process.cwd(), INTERNAL_FUNCTIONS_SRC, func)
63+
const funcDir = resolve(INTERNAL_FUNCTIONS_SRC, func)
5764
if (existsSync(funcDir)) {
5865
await rm(funcDir, { recursive: true, force: true })
5966
}

plugin/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function onPreBuild({
2424
netlifyConfig,
2525
}): Promise<void> {
2626
// Print a helpful message if the publish dir is misconfigured
27-
if (!PUBLISH_DIR || process.cwd() === PUBLISH_DIR) {
27+
if (!PUBLISH_DIR || process.cwd() === path.resolve(PUBLISH_DIR)) {
2828
utils.build.failBuild(
2929
`Gatsby sites must publish the "public" directory, but your site’s publish directory is set to “${PUBLISH_DIR}”. Please set your publish directory to your Gatsby site’s "public" directory.`,
3030
)

plugin/src/templates/api/__api.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

plugin/src/templates/api/gatsbyFunction.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ export async function gatsbyFunction(
2323
req: AugmentedGatsbyFunctionRequest,
2424
res: AugmentedGatsbyFunctionResponse,
2525
event: HandlerEvent,
26+
appDir: string,
2627
) {
28+
const functionsDir = path.join(appDir, '.cache', 'functions')
29+
2730
// Multipart form data middleware. because co-body can't handle it
2831
await new Promise((resolve) => {
2932
// As we're using a fake Express handler we need to ignore the type to keep multer happy
@@ -47,7 +50,7 @@ export async function gatsbyFunction(
4750

4851
let functions
4952
try {
50-
functions = require('../../../.cache/functions/manifest.json')
53+
functions = require(path.join(functionsDir, 'manifest.json'))
5154
} catch {
5255
return {
5356
statusCode: 404,
@@ -91,16 +94,7 @@ export async function gatsbyFunction(
9194
// During develop, the absolute path is correct, otherwise we need to use a relative path, as we're in a lambda
9295
const pathToFunction = process.env.NETLIFY_DEV
9396
? functionObj.absoluteCompiledFilePath
94-
: path.join(
95-
__dirname,
96-
'..',
97-
'..',
98-
'..',
99-
// ...We got there in the end
100-
'.cache',
101-
'functions',
102-
functionObj.relativeCompiledFilePath,
103-
)
97+
: path.join(functionsDir, functionObj.relativeCompiledFilePath)
10498

10599
if (process.env.NETLIFY_DEV && !existsSync(pathToFunction)) {
106100
// Functions are sometimes lazily-compiled, so we check and proxy the request if needed

plugin/src/templates/handlers.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { Handler, HandlerEvent } from '@netlify/functions'
1+
import type {
2+
Handler,
3+
HandlerContext,
4+
HandlerEvent,
5+
HandlerResponse,
6+
} from '@netlify/functions'
27
import { stripIndent as javascript } from 'common-tags'
38
import type { GatsbyFunctionRequest } from 'gatsby'
49
import type {
@@ -14,6 +19,8 @@ const { join } = require('path')
1419

1520
const etag = require('etag')
1621

22+
const { gatsbyFunction } = require('./api/gatsbyFunction')
23+
const { createRequestObject, createResponseObject } = require('./api/utils')
1724
const {
1825
getPagePathFromPageDataPath,
1926
getGraphQLEngine,
@@ -127,6 +134,32 @@ const getHandler = (renderMode: RenderMode, appDir: string): Handler => {
127134
}
128135
}
129136

137+
/**
138+
* Generate an API handler
139+
*/
140+
141+
const getApiHandler = (appDir: string): Handler =>
142+
function handler(
143+
event: HandlerEvent,
144+
context: HandlerContext,
145+
): Promise<HandlerResponse> {
146+
// Create a fake Gatsby/Express Request object
147+
const req = createRequestObject({ event, context })
148+
149+
return new Promise((resolve) => {
150+
try {
151+
// Create a stubbed Gatsby/Express Response object
152+
// onResEnd is the "resolve" cb for this Promise
153+
const res = createResponseObject({ onResEnd: resolve })
154+
// Try to call the actual function
155+
gatsbyFunction(req, res, event, appDir)
156+
} catch (error) {
157+
console.error(`Error executing ${event.path}`, error)
158+
resolve({ statusCode: 500 })
159+
}
160+
})
161+
}
162+
130163
export const makeHandler = (appDir: string, renderMode: RenderMode): string =>
131164
// This is a string, but if you have the right editor plugin it should format as js
132165
javascript`
@@ -141,6 +174,15 @@ export const makeHandler = (appDir: string, renderMode: RenderMode): string =>
141174
? `builder((${getHandler.toString()})("${renderMode}", pageRoot))`
142175
: `((${getHandler.toString()})("${renderMode}", pageRoot))`
143176
}
177+
`
178+
179+
export const makeApiHandler = (appDir: string): string =>
180+
// This is a string, but if you have the right editor plugin it should format as js
181+
javascript`
182+
const { gatsbyFunction } = require('./gatsbyFunction')
183+
const { createRequestObject, createResponseObject } = require('./utils')
184+
const { resolve } = require("path");
144185
186+
const pageRoot = resolve(__dirname, "${appDir}");
187+
exports.handler = ((${getApiHandler.toString()})(pageRoot))
145188
`
146-
getHandler.toString()

0 commit comments

Comments
 (0)