Skip to content

Commit

Permalink
Merge branch 'master' into feat/named-imports
Browse files Browse the repository at this point in the history
  • Loading branch information
marcolink committed Feb 6, 2023
2 parents 221a981 + 4675f3d commit baa9d88
Show file tree
Hide file tree
Showing 7 changed files with 533 additions and 5 deletions.
72 changes: 70 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [Renderer](#renderer)
- [Default Renderer](#DefaultContentTypeRenderer)
- [Localized Renderer](#LocalizedContentTypeRenderer)
- [JSDoc Renderer](#JSDocContentTypeRenderer)
- [Direct Usage](#direct-usage)
- [Browser Usage](#browser-usage)

Expand All @@ -46,6 +47,7 @@ OPTIONS
-o, --out=out output directory
-p, --preserve preserve output folder
-l, --localized add localized types
-d, --jsdoc add JSDoc comments
-s, --spaceId=spaceId space id
-t, --token=token management token
-v, --version show CLI version
Expand Down Expand Up @@ -235,7 +237,7 @@ Extend the default `BaseContentTypeRenderer` class, or implement the `ContentTyp
Relevant methods to override:

| Methods | Description | Override |
|---------------------|----------------------------------------------------------------------------|-----------------------------------------------------------------------|
| ------------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `render` | Enriches a `SourceFile` with all relevant nodes | To control content type rendering (you should know what you're doing) |
| `getContext` | Returns new render context object | To define custom type renderer and custom module name function |
| `addDefaultImports` | Define set of default imports added to every file | To control default imported modules |
Expand Down Expand Up @@ -266,11 +268,34 @@ const builder = new CFDefinitionsBuilder([
A renderer to render type fields and entry definitions. For most scenarios, this renderer is sufficient.
If no custom renderers given, `CFDefinitionsBuilder` creates a `DefaultContentTypeRenderer` by default.

#### Example Usage

```typescript
import { CFDefinitionsBuilder, DefaultContentTypeRenderer } from 'cf-content-types-generator';

const builder = new CFDefinitionsBuilder([new DefaultContentTypeRenderer()]);
```

## LocalizedContentTypeRenderer

Add additional types for localized fields. It adds utility types to transform fields into localized fields for given locales
More details on the utility types can be found here: [Issue 121](https://github.com/contentful-userland/cf-content-types-generator/issues/121)

#### Example Usage

```typescript
import {
CFDefinitionsBuilder,
DefaultContentTypeRenderer,
LocalizedContentTypeRenderer,
} from 'cf-content-types-generator';

const builder = new CFDefinitionsBuilder([
new DefaultContentTypeRenderer(),
new LocalizedContentTypeRenderer(),
]);
```

#### Example output

```typescript
Expand All @@ -293,7 +318,7 @@ export type LocalizedTypeCategory<Locales extends keyof any> = LocalizedEntry<
>;
```

#### Example usage
#### Example output usage

```typescript
const localizedCategory: LocalizedTypeCategory<'DE-de' | 'En-en'> = {
Expand All @@ -306,6 +331,48 @@ const localizedCategory: LocalizedTypeCategory<'DE-de' | 'En-en'> = {
};
```

## JSDocRenderer

Adds [JSDoc](https://jsdoc.app/) Comments to every Entry type and Field type (created by the default renderer, or a renderer that creates the same entry and field type names). This renderer can be customized through [renderer options](src/renderer/type/js-doc-renderer.ts#L20).

JSDocContentTypeRenderer can only render comments for already rendered types. It's essential to add it after the default renderer, or any renderer that creates entry and field types based on the context moduleName resolution.

#### Example Usage

```typescript
import { CFDefinitionsBuilder, JsDocRenderer } from 'cf-content-types-generator';

const builder = new CFDefinitionsBuilder([new DefaultContentTypeRenderer(), new JsDocRenderer()]);
```

#### Example output

```typescript
import * as Contentful from 'contentful';
/**
* Fields type definition for content type 'TypeAnimal'
* @name TypeAnimalFields
* @type {TypeAnimalFields}
* @memberof TypeAnimal
*/
export interface TypeAnimalFields {

/**
* Field type definition for field 'bread' (Bread)
* @name Bread
* @localized false
*/
bread: Contentful.EntryFields.Symbol;
}

/**
* Entry type definition for content type 'animal' (Animal)
* @name TypeAnimal
* @type {TypeAnimal}
*/
export type TypeAnimal = Contentful.Entry<TypeAnimalFields>;
```

# Direct Usage

If you're not a CLI person, or you want to integrate it with your tooling workflow, you can also directly use the `CFDefinitionsBuilder` from `cf-definitions-builder.ts`
Expand Down Expand Up @@ -338,6 +405,7 @@ const stringContent = new CFDefinitionsBuilder()
console.log(stringContent);

// import { Entry, EntryFields } from "contentful";

//
// export interface TypeMyEntryFields {
// myField: EntryFields.Symbol;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@oclif/plugin-help": "^5.1.12",
"contentful": "^9.1.29",
"contentful-export": "^7.17.13",
"contentful-management": "^10.27.4",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",
"ts-morph": "^17.0.1"
Expand Down
6 changes: 6 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import CFDefinitionsBuilder from './cf-definitions-builder';
import {
ContentTypeRenderer,
DefaultContentTypeRenderer,
JsDocRenderer,
LocalizedContentTypeRenderer,
} from './renderer';

Expand All @@ -20,6 +21,7 @@ class ContentfulMdg extends Command {
out: flags.string({ char: 'o', description: 'output directory' }),
preserve: flags.boolean({ char: 'p', description: 'preserve output folder' }),
localized: flags.boolean({ char: 'l', description: 'add localized types' }),
jsdoc: flags.boolean({ char: 'd', description: 'add JSDoc comments' }),

// remote access
spaceId: flags.string({ char: 's', description: 'space id' }),
Expand Down Expand Up @@ -66,6 +68,10 @@ class ContentfulMdg extends Command {
renderers.push(new LocalizedContentTypeRenderer());
}

if (flags.jsdoc) {
renderers.push(new JsDocRenderer());
}

const builder = new CFDefinitionsBuilder(renderers);
for (const model of content.contentTypes) {
builder.appendType(model);
Expand Down
1 change: 1 addition & 0 deletions src/renderer/type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export { BaseContentTypeRenderer } from './base-content-type-renderer';
export { ContentTypeRenderer } from './content-type-renderer';
export { DefaultContentTypeRenderer } from './default-content-type-renderer';
export { LocalizedContentTypeRenderer } from './localized-content-type-renderer';
export { JsDocRenderer } from './js-doc-renderer';
export { createDefaultContext } from './create-default-context';
export type { RenderContext } from './create-default-context';
166 changes: 166 additions & 0 deletions src/renderer/type/js-doc-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Field } from 'contentful';
import { ContentTypeProps } from 'contentful-management';
import { JSDocStructure, JSDocTagStructure, OptionalKind, SourceFile } from 'ts-morph';
import { CFContentType } from '../../types';
import { BaseContentTypeRenderer } from './base-content-type-renderer';

type EntryDocsOptionsProps = {
/* Name of generated Entry type */
readonly name: string;
readonly contentType: CFContentType;
};

type FieldsDocsOptionsProps = {
/* Name of generated Fields type */
readonly name: string;
readonly entryName: string;
readonly fields: Field[];
};

type FieldDocsOptionsProps = {
readonly field: Field;
};

export type JSDocRenderOptions = {
renderEntryDocs?: (props: EntryDocsOptionsProps) => OptionalKind<JSDocStructure> | string;
renderFieldsDocs?: (props: FieldsDocsOptionsProps) => OptionalKind<JSDocStructure> | string;
renderFieldDocs?: (props: FieldDocsOptionsProps) => OptionalKind<JSDocStructure> | string;
};

export const defaultJsDocRenderOptions: Required<JSDocRenderOptions> = {
renderEntryDocs: ({ contentType, name }) => {
const tags: OptionalKind<JSDocTagStructure>[] = [];

tags.push(
{
tagName: 'name',
text: name,
},
{
tagName: 'type',
text: `{${name}}`,
},
);

const cmaContentType = contentType as ContentTypeProps;

if (cmaContentType.sys.createdBy?.sys?.id) {
tags.push({
tagName: 'author',
text: cmaContentType.sys.createdBy.sys.id,
});
}

if (cmaContentType.sys.firstPublishedAt) {
tags.push({
tagName: 'since',
text: cmaContentType.sys.firstPublishedAt,
});
}

if (cmaContentType.sys.publishedVersion) {
tags.push({
tagName: 'version',
text: cmaContentType.sys.publishedVersion.toString(),
});
}

return {
description: `Entry type definition for content type '${contentType.sys.id}' (${contentType.name})`,
tags,
};
},

renderFieldsDocs: ({ name, entryName }) => {
return {
description: `Fields type definition for content type '${entryName}'`,
tags: [
{
tagName: 'name',
text: name,
},
{
tagName: 'type',
text: `{${name}}`,
},
{
tagName: 'memberof',
text: entryName,
},
],
};
},

renderFieldDocs: ({ field }) => {
return {
description: `Field type definition for field '${field.id}' (${field.name})`,
tags: [
{
tagName: 'name',
text: field.name,
},
{
tagName: 'localized',
text: field.localized.toString(),
},
],
};
},
};

/* JsDocRenderer only works in conjunction with other Renderers. It relays on previously rendered Interfaces */
export class JsDocRenderer extends BaseContentTypeRenderer {
private renderOptions: Required<JSDocRenderOptions>;

constructor({ renderOptions }: { renderOptions?: JSDocRenderOptions } = {}) {
super();
this.renderOptions = {
...defaultJsDocRenderOptions,
...renderOptions,
};
}

public render = (contentType: CFContentType, file: SourceFile): void => {
const context = this.createContext();

const entryInterfaceName = context.moduleName(contentType.sys.id);
const entryInterface = file.getTypeAlias(entryInterfaceName);

if (entryInterface) {
entryInterface.addJsDoc(
this.renderOptions.renderEntryDocs({
name: entryInterfaceName,
contentType,
}),
);
}

const fieldsInterfaceName = context.moduleFieldsName(contentType.sys.id);
const fieldsInterface = file.getInterface(fieldsInterfaceName);

if (fieldsInterface) {
fieldsInterface.addJsDoc(
this.renderOptions.renderFieldsDocs({
name: fieldsInterfaceName,
entryName: entryInterfaceName,
fields: contentType.fields,
}),
);

const fields = fieldsInterface.getProperties();

for (const field of fields) {
const fieldName = field.getName();
const contentTypeField = contentType.fields.find((f) => f.id === fieldName);

if (contentTypeField) {
field.addJsDoc(
this.renderOptions.renderFieldDocs({
field: contentTypeField,
}),
);
}
}
}
};
}
Loading

0 comments on commit baa9d88

Please sign in to comment.