Skip to content

Commit

Permalink
fix: add missing fs method rewrites to handle fetchRemoteFile in dsg/…
Browse files Browse the repository at this point in the history
…ssr engine
  • Loading branch information
pieh committed Jan 24, 2024
1 parent 8b66db1 commit 1d97c5a
Showing 1 changed file with 115 additions and 7 deletions.
122 changes: 115 additions & 7 deletions packages/gatsby/src/utils/page-ssr-module/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { promisify } from "util"

import type { IGatsbyPage } from "../../internal"
import type { ISSRData } from "./entry"
import { link } from "linkfs"
import { link, rewritableMethods as linkRewritableMethods } from "linkfs"

const cdnDatastore = `%CDN_DATASTORE_PATH%`
const PATH_PREFIX = `%PATH_PREFIX%`

function setupFsWrapper(): string {
// setup global._fsWrapper
try {
throw new Error(`read-only`)
fs.accessSync(__filename, fs.constants.W_OK)
// TODO: this seems funky - not sure if this is correct way to handle this, so just marking TODO to revisit this
return path.join(__dirname, `..`, `data`, `datastore`)
Expand All @@ -30,7 +31,7 @@ function setupFsWrapper(): string {
global.__GATSBY.root = TEMP_DIR

// TODO: don't hardcode this
const cacheDir = `/var/task/.cache`
const cacheDir = `${process.cwd()}/.cache`

// we need to rewrite fs
const rewrites = [
Expand All @@ -47,25 +48,132 @@ function setupFsWrapper(): string {
to: TEMP_CACHE_DIR,
rewrites,
})

// copied from https://github.com/streamich/linkfs/blob/master/src/index.ts#L126-L142
function mapPathUsingRewrites(fsPath: fs.PathLike): string {
let filename = path.resolve(String(fsPath))
for (const [from, to] of rewrites) {
if (filename.indexOf(from) === 0) {
const rootRegex = /(?:^[a-zA-Z]:\\$)|(?:^\/$)/ // C:\ vs /
const isRoot = from.match(rootRegex)
const baseRegex = `^(` + from.replace(/\\/g, `\\\\`) + `)`

if (isRoot) {
const regex = new RegExp(baseRegex)
filename = filename.replace(regex, () => to + path.sep)
} else {
const regex = new RegExp(baseRegex + `(\\\\|/|$)`)
filename = filename.replace(regex, (_match, _p1, p2) => to + p2)
}
}
}
return filename
}

function applyRename<
T = typeof import("fs") | typeof import("fs").promises
>(fsToRewrite: T, lfs: T, method: "rename" | "renameSync"): void {
const original = fsToRewrite[method]
if (original) {
// @ts-ignore - complains about __promisify__ which doesn't actually exist in runtime
lfs[method] = (
...args: Parameters<typeof import("fs")["rename" | "renameSync"]>
): ReturnType<typeof import("fs")["rename" | "renameSync"]> => {
args[0] = mapPathUsingRewrites(args[0])
args[1] = mapPathUsingRewrites(args[1])
// @ts-ignore - can't decide which signature this is, but we just preserve the original
// signature here and mostly care about first 2 arguments being PathLike
return original.apply(fsToRewrite, args)
}
}
}

// linkfs doesn't handle following methods, so we need to add them manually
linkRewritableMethods.push(`createWriteStream`, `createReadStream`, `rm`)

function createLinkedFS<
T = typeof import("fs") | typeof import("fs").promises
>(fsToRewrite: T): T {
const linkedFS = link(fsToRewrite, rewrites) as T

// linkfs doesn't handle `to` argument in `rename` and `renameSync` methods
applyRename(fsToRewrite, linkedFS, `rename`)
applyRename(fsToRewrite, linkedFS, `renameSync`)

return linkedFS
}

// Alias the cache dir paths to the temp dir
const lfs = link(fs, rewrites) as typeof import("fs")
const lfs = createLinkedFS(fs)

// linkfs doesn't pass across the `native` prop, which graceful-fs needs
for (const key in lfs) {
if (Object.hasOwnProperty.call(fs[key], `native`)) {
lfs[key].native = fs[key].native
}
}

const dbPath = path.join(TEMP_CACHE_DIR, `data`, `datastore`)

// 'promises' is not initially linked within the 'linkfs'
// package, and is needed by underlying Gatsby code (the
// @graphql-tools/code-file-loader)
lfs.promises = link(fs.promises, rewrites)
lfs.promises = createLinkedFS(fs.promises)

const originalWritesStream = fs.WriteStream
function LinkedWriteStream(
this: fs.WriteStream,
...args: Parameters<(typeof fs)["createWriteStream"]>
): fs.WriteStream {
args[0] = mapPathUsingRewrites(args[0])
args[1] =
typeof args[1] === `string`
? {
flags: args[1],
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options)
fs: lfs,
}
: {
...(args[1] || {}),
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options)
fs: lfs,
}

// @ts-ignore TS doesn't like extending prototype "classes"
return originalWritesStream.apply(this, args)
}
LinkedWriteStream.prototype = Object.create(originalWritesStream.prototype)
// @ts-ignore TS doesn't like extending prototype "classes"
lfs.WriteStream = LinkedWriteStream

const originalReadStream = fs.ReadStream
function LinkedReadStream(
this: fs.ReadStream,
...args: Parameters<(typeof fs)["createReadStream"]>
): fs.ReadStream {
args[0] = mapPathUsingRewrites(args[0])
args[1] =
typeof args[1] === `string`
? {
flags: args[1],
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options)
fs: lfs,
}
: {
...(args[1] || {}),
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options)
fs: lfs,
}

// @ts-ignore TS doesn't like extending prototype "classes"
return originalReadStream.apply(this, args)
}
LinkedReadStream.prototype = Object.create(originalReadStream.prototype)
// @ts-ignore TS doesn't like extending prototype "classes"
lfs.ReadStream = LinkedReadStream

const dbPath = path.join(TEMP_CACHE_DIR, `data`, `datastore`)

// Gatsby uses this instead of fs if present
// eslint-disable-next-line no-underscore-dangle
// @ts-ignore __promisify__ stuff
global._fsWrapper = lfs

if (!cdnDatastore) {
Expand Down

0 comments on commit 1d97c5a

Please sign in to comment.