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

Add support for overriding types for specific fields #76

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ module.exports = function() {
}
```

Optionally, you can add a file called `getContentfulFieldOverrides.js` in the root of your project, which should export two functions:
1. `getImports` which should return an array of import declarations (as strings) required for your overriden types.
2. `getOverridenContentTypes` that returns an object of `OverridenContentTypes` type (see example), which will indicate which fields for which content types should be overriden with the type name you provided.

Example:
```js
function getImports() {
return [
"import { SomeLibraryType } from '@some-library';"
]
}

function getOverridenContentTypes() {
return {
'myContentTypeId': {
'overridenField': 'SomeLibraryType'
}
}
}

module.exports = {
getImports,
getOverridenContentTypes,
}
```

### Command line options

```
Expand Down
28 changes: 27 additions & 1 deletion src/contentful-typescript-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import render from "./renderers/render"
import renderFieldsOnly from "./renderers/renderFieldsOnly"
import path from "path"
import { outputFileSync } from "fs-extra"
import { OverridenContentTypes } from "./lib/fieldOverrides"

const meow = require("meow")

Expand Down Expand Up @@ -67,13 +68,38 @@ async function runCodegen(outputFile: string) {
const locales = await environment.getLocales()
const outputPath = path.resolve(process.cwd(), outputFile)

let fieldOverrides: OverridenContentTypes | undefined
let extraImports: string[] | undefined
try {
const getContentfulFieldOverridesPath = path.resolve(
process.cwd(),
"./getContentfulFieldOverrides.js",
)
const { getImports, getOverridenContentTypes } = require(getContentfulFieldOverridesPath)
extraImports = getImports() as string[]
fieldOverrides = getOverridenContentTypes() as OverridenContentTypes
} catch (error) {
if (error.code === "MODULE_NOT_FOUND") {
console.warn("`getContentfulFieldOverrides` file not found, skipping...")
fieldOverrides = undefined
extraImports = undefined
} else {
throw error
}
}

let output
if (cli.flags.fieldsOnly) {
output = await renderFieldsOnly(contentTypes.items, { namespace: cli.flags.namespace })
output = await renderFieldsOnly(contentTypes.items, {
namespace: cli.flags.namespace,
fieldOverrides,
})
} else {
output = await render(contentTypes.items, locales.items, {
localization: cli.flags.localization,
namespace: cli.flags.namespace,
fieldOverrides,
extraImports,
})
}

Expand Down
20 changes: 20 additions & 0 deletions src/lib/fieldOverrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ContentType, Field } from "contentful"

export type OverridenContentTypes = Record<string, OverridenFields>

export type OverridenFields = Record<string, string>

export function getOverridenFields(
contentType: ContentType,
fieldOverrides?: OverridenContentTypes,
) {
return fieldOverrides ? fieldOverrides[contentType.sys.id] : undefined
}

export function getOverridenFieldType(field: Field, overridenFields?: OverridenFields) {
if (!overridenFields) {
return undefined
}

return overridenFields[field.id]
}
20 changes: 16 additions & 4 deletions src/renderers/contentful-fields-only/renderContentType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ContentType, Field, FieldType } from "contentful"

import {
getOverridenFields,
getOverridenFieldType,
OverridenContentTypes,
OverridenFields,
} from "../../lib/fieldOverrides"
import renderInterface from "../typescript/renderInterface"
import renderField from "../contentful/renderField"
import renderContentTypeId from "../contentful/renderContentTypeId"
Expand All @@ -14,9 +20,13 @@ import renderNumber from "../contentful/fields/renderNumber"
import renderObject from "../contentful/fields/renderObject"
import renderSymbol from "../contentful/fields/renderSymbol"

export default function renderContentType(contentType: ContentType): string {
export default function renderContentType(
contentType: ContentType,
fieldOverrides?: OverridenContentTypes,
): string {
const name = renderContentTypeId(contentType.sys.id)
const fields = renderContentTypeFields(contentType.fields)
const overridenFields = getOverridenFields(contentType, fieldOverrides)
const fields = renderContentTypeFields(contentType.fields, overridenFields)

return renderInterface({
name,
Expand All @@ -27,10 +37,12 @@ export default function renderContentType(contentType: ContentType): string {
})
}

function renderContentTypeFields(fields: Field[]): string {
function renderContentTypeFields(fields: Field[], overridenFields?: OverridenFields): string {
return fields
.filter(field => !field.omitted)
.map<string>(field => {
const overridenType = getOverridenFieldType(field, overridenFields)

const functionMap: Record<FieldType, (field: Field) => string> = {
Array: renderArray,
Boolean: renderBoolean,
Expand All @@ -45,7 +57,7 @@ function renderContentTypeFields(fields: Field[]): string {
Text: renderSymbol,
}

return renderField(field, functionMap[field.type](field))
return renderField(field, overridenType || functionMap[field.type](field))
})
.join("\n\n")
}
25 changes: 21 additions & 4 deletions src/renderers/contentful/renderContentType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ContentType, Field, FieldType, Sys } from "contentful"

import {
getOverridenFields,
getOverridenFieldType,
OverridenContentTypes,
OverridenFields,
} from "../../lib/fieldOverrides"
import renderInterface from "../typescript/renderInterface"
import renderField from "./renderField"
import renderContentTypeId from "./renderContentTypeId"
Expand All @@ -13,9 +19,14 @@ import renderObject from "./fields/renderObject"
import renderRichText from "./fields/renderRichText"
import renderSymbol from "./fields/renderSymbol"

export default function renderContentType(contentType: ContentType, localization: boolean): string {
export default function renderContentType(
contentType: ContentType,
localization: boolean,
fieldOverrides?: OverridenContentTypes,
): string {
const name = renderContentTypeId(contentType.sys.id)
const fields = renderContentTypeFields(contentType.fields, localization)
const overridenFields = getOverridenFields(contentType, fieldOverrides)
const fields = renderContentTypeFields(contentType.fields, localization, overridenFields)
const sys = renderSys(contentType.sys)

return `
Expand All @@ -34,10 +45,16 @@ function descriptionComment(description: string | undefined) {
return ""
}

function renderContentTypeFields(fields: Field[], localization: boolean): string {
function renderContentTypeFields(
fields: Field[],
localization: boolean,
overridenFields?: OverridenFields,
): string {
return fields
.filter(field => !field.omitted)
.map<string>(field => {
const overridenType = getOverridenFieldType(field, overridenFields)

const functionMap: Record<FieldType, (field: Field) => string> = {
Array: renderArray,
Boolean: renderBoolean,
Expand All @@ -52,7 +69,7 @@ function renderContentTypeFields(fields: Field[], localization: boolean): string
Text: renderSymbol,
}

return renderField(field, functionMap[field.type](field), localization)
return renderField(field, overridenType || functionMap[field.type](field), localization)
})
.join("\n\n")
}
Expand Down
26 changes: 19 additions & 7 deletions src/renderers/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,60 @@ import { ContentType, Locale } from "contentful"

import { format, resolveConfig } from "prettier"

import renderContentfulImports from "./contentful/renderContentfulImports"
import renderImports from "./renderImports"
import renderContentType from "./contentful/renderContentType"
import renderUnion from "./typescript/renderUnion"
import renderAllLocales from "./contentful/renderAllLocales"
import renderDefaultLocale from "./contentful/renderDefaultLocale"
import renderNamespace from "./contentful/renderNamespace"
import renderLocalizedTypes from "./contentful/renderLocalizedTypes"
import { OverridenContentTypes } from "../lib/fieldOverrides"

interface Options {
localization?: boolean
namespace?: string
fieldOverrides?: OverridenContentTypes
extraImports?: string[]
}

export default async function render(
contentTypes: ContentType[],
locales: Locale[],
{ namespace, localization = false }: Options = {},
{ namespace, localization = false, fieldOverrides, extraImports }: Options = {},
) {
const sortedContentTypes = contentTypes.sort((a, b) => a.sys.id.localeCompare(b.sys.id))
const sortedLocales = locales.sort((a, b) => a.code.localeCompare(b.code))

const typingsSource = [
renderAllContentTypes(sortedContentTypes, localization),
renderAllContentTypes(sortedContentTypes, localization, fieldOverrides),
renderAllContentTypeIds(sortedContentTypes),
renderAllLocales(sortedLocales),
renderDefaultLocale(sortedLocales),
renderLocalizedTypes(localization),
].join("\n\n")

const source = [
renderContentfulImports(localization),
renderImports(localization, extraImports),
renderNamespace(typingsSource, namespace),
].join("\n\n")

const prettierConfig = await resolveConfig(process.cwd())
return format(source, { ...prettierConfig, parser: "typescript" })
}

function renderAllContentTypes(contentTypes: ContentType[], localization: boolean): string {
return contentTypes.map(contentType => renderContentType(contentType, localization)).join("\n\n")
function renderAllContentTypes(
contentTypes: ContentType[],
localization: boolean,
fieldOverrides?: OverridenContentTypes,
): string {
return contentTypes
.map(contentType => renderContentType(contentType, localization, fieldOverrides))
.join("\n\n")
}

function renderAllContentTypeIds(contentTypes: ContentType[]): string {
return renderUnion("CONTENT_TYPE", contentTypes.map(contentType => `'${contentType.sys.id}'`))
return renderUnion(
"CONTENT_TYPE",
contentTypes.map(contentType => `'${contentType.sys.id}'`),
)
}
15 changes: 11 additions & 4 deletions src/renderers/renderFieldsOnly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,34 @@ import { ContentType } from "contentful"

import { format, resolveConfig } from "prettier"

import { OverridenContentTypes } from "../lib/fieldOverrides"
import renderContentType from "./contentful-fields-only/renderContentType"
import renderNamespace from "./contentful/renderNamespace"

interface Options {
namespace?: string
fieldOverrides?: OverridenContentTypes
}

export default async function renderFieldsOnly(
contentTypes: ContentType[],
{ namespace }: Options = {},
{ namespace, fieldOverrides }: Options = {},
) {
const sortedContentTypes = contentTypes.sort((a, b) => a.sys.id.localeCompare(b.sys.id))

const typingsSource = renderAllContentTypes(sortedContentTypes)
const typingsSource = renderAllContentTypes(sortedContentTypes, fieldOverrides)
const source = [renderNamespace(typingsSource, namespace)].join("\n\n")

const prettierConfig = await resolveConfig(process.cwd())

return format(source, { ...prettierConfig, parser: "typescript" })
}

function renderAllContentTypes(contentTypes: ContentType[]): string {
return contentTypes.map(contentType => renderContentType(contentType)).join("\n\n")
function renderAllContentTypes(
contentTypes: ContentType[],
fieldOverrides?: OverridenContentTypes,
): string {
return contentTypes
.map(contentType => renderContentType(contentType, fieldOverrides))
.join("\n\n")
}
18 changes: 18 additions & 0 deletions src/renderers/renderImports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import renderContentfulImports from "./contentful/renderContentfulImports"

function renderExtraImports(extraImports: string[]) {
return extraImports.join("\n")
}

export default function renderImports(
localization: boolean = false,
extraImports?: string[],
): string {
const contentfulImports = renderContentfulImports(localization)

if (!extraImports) {
return contentfulImports
}

return [contentfulImports, renderExtraImports(extraImports)].join("\n\n")
}
22 changes: 22 additions & 0 deletions test/renderers/contentful-fields-only/renderContentType.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import renderContentType from "../../../src/renderers/contentful-fields-only/renderContentType"
import { ContentType, Sys } from "contentful"
import format from "../../support/format"
import { OverridenContentTypes } from "../../../src/lib/fieldOverrides"

describe("renderContentType()", () => {
const fieldOverrides: OverridenContentTypes = {
myContentType: {
symbolField: "MyCustomType",
},
}

const contentType: ContentType = {
sys: {
id: "myContentType",
Expand Down Expand Up @@ -57,4 +64,19 @@ describe("renderContentType()", () => {
}"
`)
})

it("works with miscellaneous field types", () => {
expect(format(renderContentType(contentType, fieldOverrides))).toMatchInlineSnapshot(`
"export interface IMyContentType {
fields: {
/** Symbol Field™ */
symbolField?: MyCustomType | undefined,

/** Array field */
arrayField: (\\"one\\" | \\"of\\" | \\"the\\" | \\"above\\")[]
};
[otherKeys: string]: any;
}"
`)
})
})
Loading