From b05c174877262c399fed4c100aeb708b56f01fc4 Mon Sep 17 00:00:00 2001 From: zernie Date: Fri, 17 Apr 2020 00:23:24 +0300 Subject: [PATCH] feat: add --namespace CLI option (#22) Add `--namespace N, -n Wrap types in namespace (disabled by default)` option that will wrap the generated types in a namespace --- Example: `$ npx contentful-typescript-codegen --output ./contentful.d.ts -n Codegen` ```ts declare namespace Codegen { ... } export as namespace Codegen export = Codegen ``` --- src/contentful-typescript-codegen.ts | 9 ++- src/renderers/contentful/renderNamespace.ts | 12 +++ src/renderers/render.ts | 8 +- src/renderers/renderFieldsOnly.ts | 13 ++- test/renderers/render.test.ts | 58 +++++++++++++ test/renderers/renderFieldsOnly.test.ts | 90 ++++++++++++--------- 6 files changed, 149 insertions(+), 41 deletions(-) create mode 100644 src/renderers/contentful/renderNamespace.ts diff --git a/src/contentful-typescript-codegen.ts b/src/contentful-typescript-codegen.ts index 55f2291e..c779a52a 100644 --- a/src/contentful-typescript-codegen.ts +++ b/src/contentful-typescript-codegen.ts @@ -14,6 +14,7 @@ const cli = meow( --output, -o Where to write to --poll, -p Continuously refresh types --interval N, -i The interval in seconds at which to poll (defaults to 15) + --namespace N, -n Wrap types in namespace N (disabled by default) --fields-only Output a tree that _only_ ensures fields are valid and present, and does not provide types for Sys, Assets, or Rich Text. This is useful for ensuring raw @@ -44,6 +45,11 @@ const cli = meow( alias: "i", required: false, }, + namespace: { + type: "string", + alias: "n", + required: false, + }, localization: { type: "boolean", alias: "l", @@ -63,10 +69,11 @@ async function runCodegen(outputFile: string) { let output if (cli.flags.fieldsOnly) { - output = await renderFieldsOnly(contentTypes.items) + output = await renderFieldsOnly(contentTypes.items, { namespace: cli.flags.namespace }) } else { output = await render(contentTypes.items, locales.items, { localization: cli.flags.localization, + namespace: cli.flags.namespace, }) } diff --git a/src/renderers/contentful/renderNamespace.ts b/src/renderers/contentful/renderNamespace.ts new file mode 100644 index 00000000..9d1c0160 --- /dev/null +++ b/src/renderers/contentful/renderNamespace.ts @@ -0,0 +1,12 @@ +export default function renderNamespace(source: string, namespace: string | undefined) { + if (!namespace) return source + + return ` + declare namespace ${namespace} { + ${source} + } + + export as namespace ${namespace} + export=${namespace} + ` +} diff --git a/src/renderers/render.ts b/src/renderers/render.ts index 65e2a962..a9558737 100644 --- a/src/renderers/render.ts +++ b/src/renderers/render.ts @@ -7,21 +7,23 @@ 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" interface Options { localization?: boolean + namespace?: string } export default async function render( contentTypes: ContentType[], locales: Locale[], - { localization = false }: Options = {}, + { namespace, localization = false }: 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 source = [ + const typingsSource = [ renderContentfulImports(localization), renderAllContentTypes(sortedContentTypes, localization), renderAllContentTypeIds(sortedContentTypes), @@ -30,6 +32,8 @@ export default async function render( renderLocalizedTypes(localization), ].join("\n\n") + const source = [renderContentfulImports(), renderNamespace(typingsSource, namespace)].join("\n\n") + const prettierConfig = await resolveConfig(process.cwd()) return format(source, { ...prettierConfig, parser: "typescript" }) } diff --git a/src/renderers/renderFieldsOnly.ts b/src/renderers/renderFieldsOnly.ts index 82a0d04b..6cae24d3 100644 --- a/src/renderers/renderFieldsOnly.ts +++ b/src/renderers/renderFieldsOnly.ts @@ -3,11 +3,20 @@ import { ContentType } from "contentful" import { format, resolveConfig } from "prettier" import renderContentType from "./contentful-fields-only/renderContentType" +import renderNamespace from "./contentful/renderNamespace" -export default async function renderFieldsOnly(contentTypes: ContentType[]) { +interface Options { + namespace?: string +} + +export default async function renderFieldsOnly( + contentTypes: ContentType[], + { namespace }: Options = {}, +) { const sortedContentTypes = contentTypes.sort((a, b) => a.sys.id.localeCompare(b.sys.id)) - const source = renderAllContentTypes(sortedContentTypes) + const typingsSource = renderAllContentTypes(sortedContentTypes) + const source = [renderNamespace(typingsSource, namespace)].join("\n\n") const prettierConfig = await resolveConfig(process.cwd()) diff --git a/test/renderers/render.test.ts b/test/renderers/render.test.ts index 1ade6004..c7f4ba5c 100644 --- a/test/renderers/render.test.ts +++ b/test/renderers/render.test.ts @@ -58,6 +58,11 @@ describe("render()", () => { import { Asset, Entry } from \\"contentful\\" import { Document } from \\"@contentful/rich-text-types\\" + // THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT. + + import { Asset, Entry } from \\"contentful\\" + import { Document } from \\"@contentful/rich-text-types\\" + export interface IMyContentTypeFields { /** Array field */ arrayField: (\\"one\\" | \\"of\\" | \\"the\\" | \\"above\\")[] @@ -125,6 +130,11 @@ describe("render()", () => { expect(await render(contentTypes, locales, { localization: true })).toMatchInlineSnapshot(` "// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT. + import { Asset, Entry } from \\"contentful\\" + import { Document } from \\"@contentful/rich-text-types\\" + + // THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT. + import { Entry } from \\"contentful\\" import { Document } from \\"@contentful/rich-text-types\\" @@ -182,4 +192,52 @@ describe("render()", () => { " `) }) + + it("renders given a content type inside a namespace", async () => { + expect(await render(contentTypes, locales, { namespace: "Codegen" })).toMatchInlineSnapshot(` + "// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT. + + import { Asset, Entry } from \\"contentful\\" + import { Document } from \\"@contentful/rich-text-types\\" + + declare namespace Codegen { + // THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT. + + import { Asset, Entry } from \\"contentful\\" + import { Document } from \\"@contentful/rich-text-types\\" + + export interface IMyContentTypeFields { + /** Array field */ + arrayField: (\\"one\\" | \\"of\\" | \\"the\\" | \\"above\\")[] + } + + export interface IMyContentType extends Entry { + sys: { + id: string + type: string + createdAt: string + updatedAt: string + locale: string + contentType: { + sys: { + id: \\"myContentType\\" + linkType: \\"ContentType\\" + type: \\"Link\\" + } + } + } + } + + export type CONTENT_TYPE = \\"myContentType\\" + + export type LOCALE_CODE = \\"en-US\\" | \\"pt-BR\\" + + export type CONTENTFUL_DEFAULT_LOCALE_CODE = \\"en-US\\" + } + + export as namespace Codegen + export = Codegen + " + `) + }) }) diff --git a/test/renderers/renderFieldsOnly.test.ts b/test/renderers/renderFieldsOnly.test.ts index b40755d2..2e299265 100644 --- a/test/renderers/renderFieldsOnly.test.ts +++ b/test/renderers/renderFieldsOnly.test.ts @@ -2,47 +2,65 @@ import renderFieldsOnly from "../../src/renderers/renderFieldsOnly" import { ContentType, Sys } from "contentful" describe("renderFieldsOnly()", () => { - it("renders given a content type", async () => { - const contentTypes: ContentType[] = [ - { - sys: { - id: "myContentType", - } as Sys, - fields: [ - { - id: "arrayField", - name: "Array field", - required: true, - validations: [{}], - items: { - type: "Symbol", - validations: [ - { - in: ["one", "of", "the", "above"], - }, - ], - }, - disabled: false, - omitted: false, - localized: false, - type: "Array", + const contentTypes: ContentType[] = [ + { + sys: { + id: "myContentType", + } as Sys, + fields: [ + { + id: "arrayField", + name: "Array field", + required: true, + validations: [{}], + items: { + type: "Symbol", + validations: [ + { + in: ["one", "of", "the", "above"], + }, + ], }, - ], - description: "", - displayField: "", - name: "", - toPlainObject: () => ({} as ContentType), - }, - ] + disabled: false, + omitted: false, + localized: false, + type: "Array", + }, + ], + description: "", + displayField: "", + name: "", + toPlainObject: () => ({} as ContentType), + }, + ] + it("renders a given content type", async () => { expect(await renderFieldsOnly(contentTypes)).toMatchInlineSnapshot(` - "export interface IMyContentType { - fields: { - /** Array field */ - arrayField: (\\"one\\" | \\"of\\" | \\"the\\" | \\"above\\")[] + "export interface IMyContentType { + fields: { + /** Array field */ + arrayField: (\\"one\\" | \\"of\\" | \\"the\\" | \\"above\\")[] + } + [otherKeys: string]: any + } + " + `) + }) + + it("renders a given content type inside a namespace", async () => { + expect(await renderFieldsOnly(contentTypes, { namespace: "Codegen" })).toMatchInlineSnapshot(` + "declare namespace Codegen { + export interface IMyContentType { + fields: { + /** Array field */ + arrayField: (\\"one\\" | \\"of\\" | \\"the\\" | \\"above\\")[] + } + [otherKeys: string]: any } - [otherKeys: string]: any } + + export as namespace Codegen + export = Codegen " `) })