Skip to content

Commit

Permalink
Merge pull request #68 from RKcode/feature/handle-resolve-links-option
Browse files Browse the repository at this point in the history
Feat: Add `resolveLinks` option to improve MultilinkStoryblok
  • Loading branch information
dohomi authored Apr 8, 2024
2 parents 1291efa + e0f7d69 commit cadb0c7
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 89 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Example:
- target *optional default: storyblok-component-types.d.ts
- titlePrefix *optional default: '_storyblok'
- titleSuffix *optional
- resolveLinks *optional
- compilerOptions.[property] *optional
- customTypeParser *optional - path to a custom parser NodeJS file
```
Expand Down Expand Up @@ -79,6 +80,8 @@ storyblokToTypescript({
titlePrefix: '',
// optional type name suffix (default: [Name]_Storyblok)
titleSuffix: '_storyblok',
// optional resolveLinks (default: story)
resolveLinks: "url",
// optional compilerOptions which get passed through to json-schema-to-typescript
compilerOptions: {
unknownAny: false,
Expand Down
24 changes: 23 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import storyblokToTypescript from './index'
import {resolve} from 'path'
import {CliOptions, StoryblokTsOptions} from "./typings";
import {CliOptions, ResolveLinkOption, StoryblokTsOptions} from "./typings";
import * as fs from 'fs';
import {JSONSchema4} from "json-schema";

Expand All @@ -14,6 +14,9 @@ const parseValue = (value: string) => {
if (value.match(/^[\d.]+$/)) {
return Number(value)
}
if (value.includes(',')) {
return value.split(',')
}
return value
}

Expand Down Expand Up @@ -70,6 +73,21 @@ if (!props.source) {
process.exit()
}

const isResolveLinkOption = (param: string): param is ResolveLinkOption =>
["url", "link", "story"].includes(param)

const isValidResolveLinkOption = (
param?: string | string[]
): param is ResolveLinkOption | ResolveLinkOption[] =>
(typeof param === "string" && isResolveLinkOption(param)) ||
(Array.isArray(param) && param.every(isResolveLinkOption))

const resolveLinks = props.resolveLinks
if (resolveLinks !== undefined && !isValidResolveLinkOption(resolveLinks)) {
console.log('resolveLinks must be a string with values "url", "link" or "story" separated by commas')
process.exit()
}

if (props.target && !props.target.endsWith('.ts')) {
props.target += '.d.ts'
}
Expand All @@ -83,6 +101,10 @@ getDataFromPath(props.source).then((rawComponents) => {
compilerOptions: props.compilerOptions || {},
path: resolve(props.target || './storyblok-component-types.d.ts')
}

if (resolveLinks) {
options.resolveLinks = Array.isArray(resolveLinks) ? resolveLinks : [resolveLinks]
}

if (props.titlePrefix !== undefined) {
options.titlePrefix = props.titlePrefix
Expand Down
248 changes: 162 additions & 86 deletions src/genericTypes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { JSONSchema4 } from 'json-schema';
import { compile } from 'json-schema-to-typescript';

import { BasicType, CompilerOptions } from './typings';
import { BasicType, CompilerOptions, ResolveLinkOption, StoryblokResolveOptions } from './typings';

const typeFuncs: {
[k in BasicType]: (name: string, options: CompilerOptions) => Promise<string | undefined>
[k in BasicType]: (name: string, options: CompilerOptions, storyblokResolve: StoryblokResolveOptions) => Promise<string | undefined>
} = {
'asset': generateAssetTypeIfNotYetGenerated,
'multiasset': generateMultiAssetTypeIfNotYetGenerated,
Expand All @@ -21,8 +21,8 @@ async function compileType(obj: JSONSchema4, name: BasicType, compilerOptions: C
return ts
}

export async function generate(type: BasicType, title: string, compilerOptions: CompilerOptions) {
return await typeFuncs[type](title, compilerOptions)
export async function generate(type: BasicType, title: string, compilerOptions: CompilerOptions, storyblokResolve: StoryblokResolveOptions) {
return await typeFuncs[type](title, compilerOptions, storyblokResolve)
}

export const TYPES = Object.keys(typeFuncs)
Expand Down Expand Up @@ -154,7 +154,161 @@ async function generateMultiAssetTypeIfNotYetGenerated(title: string, compilerOp
}
}

async function generateMultiLinkTypeIfNotYetGenerated(title: string, compilerOptions: CompilerOptions) {
function getStoryLinkTypeByResolveLink(resolveLinkOption: ResolveLinkOption): JSONSchema4 {
switch (resolveLinkOption) {
case "url":
return {
type: 'object',
required: ['name', 'id', 'uuid', 'slug', 'url', 'full_slug'],
properties: {
name: {
type: "string"
},
id: {
type: "integer"
},
uuid: {
type: "string",
format: "uuid"
},
slug: {
type: "string"
},
url: {
type: "string"
},
full_slug: {
type: "string"
}
}
}
case "link":
return {
type: 'object',
required: ['name', 'id', 'uuid', 'slug'],
properties: {
name: {
type: "string"
},
id: {
type: "integer"
},
uuid: {
type: "string",
format: "uuid"
},
slug: {
type: "string"
},
position: {
type: "integer"
},
is_folder: {
type: "boolean"
},
is_startpage: {
type: "boolean"
},
parent_id: {
type: ["null", "integer"]
},
published: {
type: "boolean"
},
path: {
type: ["null", "string"]
},
real_path: {
type: ["null", "string"]
},
}
}
default:
return {
type: 'object',
required: ['name', 'id', 'uuid', 'slug', 'url', 'full_slug'],
properties: {
name: {
type: "string"
},
created_at: {
type: "string",
format: "date-time"
},
published_at: {
type: "string",
format: "date-time"
},
id: {
type: "integer"
},
uuid: {
type: "string",
format: "uuid"
},
content: {
type: "object"
},
slug: {
type: "string"
},
full_slug: {
type: "string"
},
sort_by_date: {
type: ["null", "string"],
format: "date-time"
},
position: {
type: "integer"
},
tag_list: {
type: "array",
items: {
type: "string"
}
},
is_startpage: {
type: "boolean"
},
parent_id: {
type: ["null", "integer"]
},
meta_data: {
type: ["null", "object"]
},
group_id: {
type: "string",
format: "uuid"
},
first_published_at: {
type: "string",
format: "date-time"
},
release_id: {
type: ["null", "integer"]
},
lang: {
type: "string"
},
path: {
type: ["null", "string"]
},
alternates: {
type: "array"
},
default_full_slug: {
type: ["null", "string"]
},
translated_slugs: {
type: ["null", "array"]
}
}
}
}
}

async function generateMultiLinkTypeIfNotYetGenerated(title: string, compilerOptions: CompilerOptions, storyblokResolve: StoryblokResolveOptions) {
if (!toGenerateWhitelist.includes("multilink")) return;
const obj: JSONSchema4 = {
$id: '#/multilink',
Expand All @@ -180,87 +334,9 @@ async function generateMultiLinkTypeIfNotYetGenerated(title: string, compilerOpt
type: 'string',
enum: ['_self', '_blank'],
},
story: {
type: 'object',
required: ['name', 'id', 'uuid', 'slug', 'full_slug'],
properties: {
name: {
type: "string"
},
created_at: {
type: "string",
format: "date-time"
},
published_at: {
type: "string",
format: "date-time"
},
id: {
type: "integer"
},
uuid: {
type: "string",
format: "uuid"
},
content: {
type: "object"
},
slug: {
type: "string"
},
full_slug: {
type: "string"
},
sort_by_date: {
type: ["null", "string"],
format: "date-time"
},
position: {
type: "integer"
},
tag_list: {
type: "array",
items: {
type: "string"
}
},
is_startpage: {
type: "boolean"
},
parent_id: {
type: ["null", "integer"]
},
meta_data: {
type: ["null", "object"]
},
group_id: {
type: "string",
format: "uuid"
},
first_published_at: {
type: "string",
format: "date-time"
},
release_id: {
type: ["null", "integer"]
},
lang: {
type: "string"
},
path: {
type: ["null", "string"]
},
alternates: {
type: "array"
},
default_full_slug: {
type: ["null", "string"]
},
translated_slugs: {
type: ["null", "array"]
}
}
}
...(storyblokResolve.resolveLinks.length
? { story: { oneOf: storyblokResolve.resolveLinks.map(getStoryLinkTypeByResolveLink) } }
: {}),
}
},
{
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export default async function storyblokToTypescript({
customTypeParser,
path = 'src/typings/generated/components-schema.ts',
titleSuffix = '_storyblok',
titlePrefix = ''
titlePrefix = '',
resolveLinks = []
}: StoryblokTsOptions) {

const storyblokResolveOptions = { resolveLinks };
compilerOptions = {
unknownAny: false,
bannerComment: '',
Expand Down Expand Up @@ -97,7 +99,7 @@ export default async function storyblokToTypescript({
const type = schemaElement.type

if (TYPES.includes(type)) {
const ts = await generate(type, getTitle(type), compilerOptions)
const ts = await generate(type, getTitle(type), compilerOptions, storyblokResolveOptions)

if (ts) {
tsString.push(ts)
Expand Down
8 changes: 8 additions & 0 deletions src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,28 @@ export type BasicType = 'asset' | 'multiasset' | 'multilink' | 'table' | 'richte

export type CompilerOptions = Partial<Options>;

export type ResolveLinkOption = "url" | "link" | "story"

export interface StoryblokResolveOptions {
resolveLinks: ResolveLinkOption[]
}

export interface StoryblokTsOptions {
componentsJson: {
components: JSONSchema4[]
},
customTypeParser?: (key: string, options: JSONSchema4) => void
compilerOptions?: CompilerOptions
path?: string
resolveLinks?: ResolveLinkOption[]
titleSuffix?: string
titlePrefix?: string
}

export interface CliOptions {
source: string
target?: string
resolveLinks?: string | string[]
titleSuffix?: string
titlePrefix?: string
customTypeParser?: string
Expand Down

0 comments on commit cadb0c7

Please sign in to comment.