-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support typescript config (#110)
* feat: support `getContentfulEnvironment.ts` * fix: loosen types of load environment * fix: add `ts-node` as an optional peer dependency * refactor: move exported types and functions to top of file * chore: add todo comment * chore: bump `ts-node` to v10.6.0 or higher * test: add extra case + deduplicate * docs: update readme with typescript config * Fix typo Co-authored-by: Gabriel Anca <GabrielAnca@users.noreply.github.com>
- Loading branch information
1 parent
319ce84
commit 90c2a17
Showing
6 changed files
with
327 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import * as path from "path" | ||
import * as fs from "fs" | ||
import { ContentfulCollection, ContentTypeCollection, LocaleCollection } from "contentful" | ||
|
||
// todo: switch to contentful-management interfaces here | ||
export interface ContentfulEnvironment { | ||
getContentTypes(options: { limit: number }): Promise<ContentfulCollection<unknown>> | ||
getLocales(): Promise<ContentfulCollection<unknown>> | ||
} | ||
|
||
export type EnvironmentGetter = () => Promise<ContentfulEnvironment> | ||
|
||
export async function loadEnvironment() { | ||
try { | ||
const getEnvironment = getEnvironmentGetter() | ||
const environment = await getEnvironment() | ||
|
||
return { | ||
contentTypes: (await environment.getContentTypes({ limit: 1000 })) as ContentTypeCollection, | ||
locales: (await environment.getLocales()) as LocaleCollection, | ||
} | ||
} finally { | ||
if (registerer) { | ||
registerer.enabled(false) | ||
} | ||
} | ||
} | ||
|
||
/* istanbul ignore next */ | ||
const interopRequireDefault = (obj: any): { default: any } => | ||
obj && obj.__esModule ? obj : { default: obj } | ||
|
||
type Registerer = { enabled(value: boolean): void } | ||
|
||
let registerer: Registerer | null = null | ||
|
||
function enableTSNodeRegisterer() { | ||
if (registerer) { | ||
registerer.enabled(true) | ||
|
||
return | ||
} | ||
|
||
try { | ||
registerer = require("ts-node").register() as Registerer | ||
registerer.enabled(true) | ||
} catch (e) { | ||
if (e.code === "MODULE_NOT_FOUND") { | ||
throw new Error( | ||
`'ts-node' is required for TypeScript configuration files. Make sure it is installed\nError: ${e.message}`, | ||
) | ||
} | ||
|
||
throw e | ||
} | ||
} | ||
|
||
function determineEnvironmentPath() { | ||
const pathWithoutExtension = path.resolve(process.cwd(), "./getContentfulEnvironment") | ||
|
||
if (fs.existsSync(`${pathWithoutExtension}.ts`)) { | ||
return `${pathWithoutExtension}.ts` | ||
} | ||
|
||
return `${pathWithoutExtension}.js` | ||
} | ||
|
||
function getEnvironmentGetter(): EnvironmentGetter { | ||
const getEnvironmentPath = determineEnvironmentPath() | ||
|
||
if (getEnvironmentPath.endsWith(".ts")) { | ||
enableTSNodeRegisterer() | ||
|
||
return interopRequireDefault(require(getEnvironmentPath)).default | ||
} | ||
|
||
return require(getEnvironmentPath) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import * as fs from "fs" | ||
import { loadEnvironment } from "../src/loadEnvironment" | ||
|
||
const contentfulEnvironment = () => ({ | ||
getContentTypes: () => [], | ||
getLocales: () => [], | ||
}) | ||
|
||
const getContentfulEnvironmentFileFactory = jest.fn((_type: string) => contentfulEnvironment) | ||
|
||
jest.mock( | ||
require("path").resolve(process.cwd(), "./getContentfulEnvironment.js"), | ||
() => getContentfulEnvironmentFileFactory("js"), | ||
{ virtual: true }, | ||
) | ||
|
||
jest.mock( | ||
require("path").resolve(process.cwd(), "./getContentfulEnvironment.ts"), | ||
() => getContentfulEnvironmentFileFactory("ts"), | ||
{ virtual: true }, | ||
) | ||
|
||
const tsNodeRegistererEnabled = jest.fn() | ||
const tsNodeRegister = jest.fn() | ||
|
||
jest.mock("ts-node", () => ({ register: tsNodeRegister })) | ||
|
||
describe("loadEnvironment", () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks() | ||
jest.restoreAllMocks() | ||
jest.resetModules() | ||
|
||
getContentfulEnvironmentFileFactory.mockReturnValue(contentfulEnvironment) | ||
tsNodeRegister.mockReturnValue({ enabled: tsNodeRegistererEnabled }) | ||
}) | ||
|
||
describe("when getContentfulEnvironment.ts exists", () => { | ||
beforeEach(() => { | ||
jest.spyOn(fs, "existsSync").mockReturnValue(true) | ||
}) | ||
|
||
describe("when ts-node is not found", () => { | ||
beforeEach(() => { | ||
// technically this is throwing after the `require` call, | ||
// but it still tests the same code path so is fine | ||
tsNodeRegister.mockImplementation(() => { | ||
throw new (class extends Error { | ||
public code: string | ||
|
||
constructor(message?: string) { | ||
super(message) | ||
this.code = "MODULE_NOT_FOUND" | ||
} | ||
})() | ||
}) | ||
}) | ||
|
||
it("throws a nice error", async () => { | ||
await expect(loadEnvironment()).rejects.toThrow( | ||
"'ts-node' is required for TypeScript configuration files", | ||
) | ||
}) | ||
}) | ||
|
||
describe("when there is another error", () => { | ||
beforeEach(() => { | ||
tsNodeRegister.mockImplementation(() => { | ||
throw new Error("something else went wrong!") | ||
}) | ||
}) | ||
|
||
it("re-throws", async () => { | ||
await expect(loadEnvironment()).rejects.toThrow("something else went wrong!") | ||
}) | ||
}) | ||
|
||
describe("when called multiple times", () => { | ||
it("re-uses the registerer", async () => { | ||
await loadEnvironment() | ||
await loadEnvironment() | ||
|
||
expect(tsNodeRegister).toHaveBeenCalledTimes(1) | ||
}) | ||
}) | ||
|
||
it("requires the typescript config", async () => { | ||
await loadEnvironment() | ||
|
||
expect(getContentfulEnvironmentFileFactory).toHaveBeenCalledWith("ts") | ||
expect(getContentfulEnvironmentFileFactory).not.toHaveBeenCalledWith("js") | ||
}) | ||
|
||
it("disables the registerer afterwards", async () => { | ||
await loadEnvironment() | ||
|
||
expect(tsNodeRegistererEnabled).toHaveBeenCalledWith(false) | ||
}) | ||
}) | ||
|
||
it("requires the javascript config", async () => { | ||
jest.spyOn(fs, "existsSync").mockReturnValue(false) | ||
|
||
await loadEnvironment() | ||
|
||
expect(getContentfulEnvironmentFileFactory).toHaveBeenCalledWith("js") | ||
expect(getContentfulEnvironmentFileFactory).not.toHaveBeenCalledWith("ts") | ||
}) | ||
}) |
Oops, something went wrong.