From b6d9442a35e91303237284012c19f5c5006b4a07 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 24 Jan 2024 15:57:08 +0100 Subject: [PATCH] fix: add missing fs method rewrites to handle fetchRemoteFile in dsg/ssr engine --- .../src/utils/page-ssr-module/lambda.ts | 121 +++++++++++++++++- 1 file changed, 114 insertions(+), 7 deletions(-) diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts index c60cb5f37e77f..81ae8f6938eff 100644 --- a/packages/gatsby/src/utils/page-ssr-module/lambda.ts +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -10,7 +10,7 @@ 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%` @@ -30,7 +30,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 = [ @@ -47,8 +47,63 @@ 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 + ): ReturnType => { + 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) { @@ -56,16 +111,68 @@ function setupFsWrapper(): string { 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) {