diff --git a/packages/gatsby/src/utils/__tests__/node-manifest.ts b/packages/gatsby/src/utils/__tests__/node-manifest.ts index 2c1aded68a754..beaee349369e0 100644 --- a/packages/gatsby/src/utils/__tests__/node-manifest.ts +++ b/packages/gatsby/src/utils/__tests__/node-manifest.ts @@ -12,6 +12,10 @@ import { processNodeManifests, } from "../node-manifest" +function itWindows(name: string, fn: () => void): void { + return process.platform === `win32` ? it(name, fn) : xit(name, fn) +} + jest.mock(`fs-extra`, () => { return { ensureDir: jest.fn(), @@ -342,4 +346,57 @@ describe(`processNodeManifests`, () => { await testProcessNodeManifests() process.env.NODE_ENV = `test` }) + + itWindows(`replaces reserved Windows characters with a dash`, async () => { + const nodes = [ + { id: `1`, usePageContextId: true }, + { id: `2`, useOwnerNodeId: true }, + { id: `3`, useQueryTracking: true }, + ] + mockGetNodes( + new Map(nodes.map(node => [`${node.id}`, { id: `${node.id}` }])) + ) + + store.getState.mockImplementation(() => { + return { + ...storeState, + pages: new Map([ + [ + `/test`, + { + path: `/test`, + context: { id: `1` }, + }, + ], + [ + `/test-2`, + { + path: `/test-2`, + context: { id: `2` }, + }, + ], + ]), + nodeManifests: [ + { + pluginName: `test`, + node: { id: `1` }, + // A manifest id containing all of the reserved windows characters that we check + // for and replace + manifestId: `\\*"<>/:?|`, + }, + ], + queries: { + byNode: new Map([ + [`1`, new Set([`/test`, `/test-2`])], + [`2`, new Set([`/test`, `/test-2`])], + ]), + }, + } + }) + + await processNodeManifests() + const nodeManifestFileName = path.basename(fs.writeJSON.mock.calls[0][0]) + + expect(nodeManifestFileName).toEqual(`---------.json`) + }) }) diff --git a/packages/gatsby/src/utils/node-manifest.ts b/packages/gatsby/src/utils/node-manifest.ts index 9f6290e941715..58ddd0b66dcab 100644 --- a/packages/gatsby/src/utils/node-manifest.ts +++ b/packages/gatsby/src/utils/node-manifest.ts @@ -278,13 +278,33 @@ export async function processNodeManifest( const gatsbySiteDirectory = store.getState().program.directory + let fileNameBase = inputManifest.manifestId + + /** + * Windows has a handful of special/reserved characters that are not valid in a file path + * @reference https://superuser.com/questions/358855/what-characters-are-safe-in-cross-platform-file-names-for-linux-windows-and-os + * + * The two exceptions to the list linked above are + * - the colon that is part of the hard disk partition name at the beginning of a file path (i.e. C:) + * - backslashes. We don't want to replace backslashes because those are used to delineate what the actual file path is + * + * During local development, node manifests can be written to disk but are generally unused as they are only used + * for Content Sync which runs in Gatsby Cloud. Gatsby cloud is a Linux environment in which these special chars are valid in + * filepaths. To avoid errors on Windows, we replace all instances of the special chars in the filepath (with the exception of the + * hard disk partition name) with "-" to ensure that local Windows development setups do not break when attempting + * to write one of these manifests to disk. + */ + if (process.platform === `win32`) { + fileNameBase = fileNameBase.replace(/:|\/|\*|\?|"|<|>|\||\\/g, `-`) + } + // write out the manifest file const manifestFilePath = path.join( gatsbySiteDirectory, `public`, `__node-manifests`, inputManifest.pluginName, - `${inputManifest.manifestId}.json` + `${fileNameBase}.json` ) const manifestFileDir = path.dirname(manifestFilePath)