Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support typescript config #110

Merged
merged 10 commits into from
Dec 15, 2022
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@
"meow": "^9.0.0"
},
"peerDependencies": {
"prettier": ">= 1"
"prettier": ">= 1",
"ts-node": ">= 9.0.0"
},
"peerDependenciesMeta": {
"ts-node": {
"optional": true
}
},
"devDependencies": {
"@contentful/rich-text-types": "^13.4.0",
Expand All @@ -59,6 +65,7 @@
"rollup-plugin-typescript2": "^0.22.1",
"semantic-release": "^17.4.1",
"ts-jest": "^26.0.0",
"ts-node": "^10.4.0",
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
"tslint": "^5.18.0",
"tslint-config-prettier": "^1.18.0",
"tslint-config-standard": "^8.0.1",
Expand Down
9 changes: 4 additions & 5 deletions src/contentful-typescript-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import render from "./renderers/render"
import renderFieldsOnly from "./renderers/renderFieldsOnly"
import path from "path"
import { outputFileSync } from "fs-extra"
import { loadEnvironment } from "./loadEnvironment"

const meow = require("meow")

export { ContentfulEnvironment, EnvironmentGetter } from "./loadEnvironment"

const cli = meow(
`
Usage
Expand Down Expand Up @@ -60,11 +63,7 @@ const cli = meow(
)

async function runCodegen(outputFile: string) {
const getEnvironmentPath = path.resolve(process.cwd(), "./getContentfulEnvironment.js")
const getEnvironment = require(getEnvironmentPath)
const environment = await getEnvironment()
const contentTypes = await environment.getContentTypes({ limit: 1000 })
const locales = await environment.getLocales()
const { contentTypes, locales } = await loadEnvironment()
const outputPath = path.resolve(process.cwd(), outputFile)

let output
Expand Down
78 changes: 78 additions & 0 deletions src/loadEnvironment.ts
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 */
GabrielAnca marked this conversation as resolved.
Show resolved Hide resolved
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
GabrielAnca marked this conversation as resolved.
Show resolved Hide resolved
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)
}
104 changes: 104 additions & 0 deletions test/loadEnvironment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as fs from "fs"
import { loadEnvironment } from "../src/loadEnvironment"

const getContentfulEnvironmentFileFactory = jest.fn((_type: string) => {
GabrielAnca marked this conversation as resolved.
Show resolved Hide resolved
return () => ({
getContentTypes: () => [],
getLocales: () => [],
})
})

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()

getContentfulEnvironmentFileFactory.mockReturnValue(() => ({
getContentTypes: () => [],
getLocales: () => [],
}))
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
tsNodeRegister.mockReturnValue({ enabled: tsNodeRegistererEnabled })
})

describe("when getContentfulEnvironment.ts exists", () => {
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
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("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")
})
})
77 changes: 76 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,18 @@
resolved "https://registry.yarnpkg.com/@contentful/rich-text-types/-/rich-text-types-13.4.0.tgz#a59c311ebd1b801ee00edbc08663c8d78da26171"
integrity sha512-YPdYqGmWiGAood7ri2BUXfPQKNthkQYV1rmQdaq4UAxWK5NLB5NXckaUYLbohqhHKtrq4tnfzVn+ePWID7Dzbg==

"@cspotcode/[email protected]":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==

"@cspotcode/[email protected]":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
dependencies:
"@cspotcode/source-map-consumer" "0.8.0"

"@iarna/cli@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@iarna/cli/-/cli-1.2.0.tgz#0f7af5e851afe895104583c4ca07377a8094d641"
Expand Down Expand Up @@ -759,6 +771,26 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==

"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==

"@tsconfig/node12@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c"
integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==

"@tsconfig/node14@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==

"@tsconfig/node16@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==

"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.12"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
Expand Down Expand Up @@ -960,6 +992,11 @@ acorn-walk@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==

acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==

acorn@^7.1.0, acorn@^7.1.1:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
Expand All @@ -970,6 +1007,11 @@ acorn@^8.0.5:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe"
integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==

acorn@^8.4.1:
version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==

agent-base@4, agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
Expand Down Expand Up @@ -1135,6 +1177,11 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"

arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==

argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
Expand Down Expand Up @@ -2052,6 +2099,11 @@ create-error-class@^3.0.0:
dependencies:
capture-stack-trace "^1.0.0"

create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==

cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
Expand Down Expand Up @@ -5073,7 +5125,7 @@ make-dir@^3.0.0:
dependencies:
semver "^6.0.0"

[email protected]:
[email protected], make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
Expand Down Expand Up @@ -7725,6 +7777,24 @@ ts-jest@^26.0.0:
semver "7.x"
yargs-parser "20.x"

ts-node@^10.4.0:
version "10.4.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7"
integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==
dependencies:
"@cspotcode/source-map-support" "0.7.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
yn "3.1.1"

[email protected]:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
Expand Down Expand Up @@ -8406,3 +8476,8 @@ yargs@^8.0.2:
which-module "^2.0.0"
y18n "^3.2.1"
yargs-parser "^7.0.0"

[email protected]:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==