diff --git a/README.md b/README.md index dbc924e..d65aae5 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -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, diff --git a/src/cli.ts b/src/cli.ts index f4013f5..12888cb 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -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"; @@ -14,6 +14,9 @@ const parseValue = (value: string) => { if (value.match(/^[\d.]+$/)) { return Number(value) } + if (value.includes(',')) { + return value.split(',') + } return value } @@ -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' } @@ -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 diff --git a/src/genericTypes.ts b/src/genericTypes.ts index aeb5c67..d246919 100644 --- a/src/genericTypes.ts +++ b/src/genericTypes.ts @@ -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 + [k in BasicType]: (name: string, options: CompilerOptions, storyblokResolve: StoryblokResolveOptions) => Promise } = { 'asset': generateAssetTypeIfNotYetGenerated, 'multiasset': generateMultiAssetTypeIfNotYetGenerated, @@ -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) @@ -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', @@ -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) } } + : {}), } }, { diff --git a/src/index.ts b/src/index.ts index 3ef4469..f1ec364 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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: '', @@ -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) diff --git a/src/typings.ts b/src/typings.ts index ec8ec6b..7f63365 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -20,6 +20,12 @@ export type BasicType = 'asset' | 'multiasset' | 'multilink' | 'table' | 'richte export type CompilerOptions = Partial; +export type ResolveLinkOption = "url" | "link" | "story" + +export interface StoryblokResolveOptions { + resolveLinks: ResolveLinkOption[] +} + export interface StoryblokTsOptions { componentsJson: { components: JSONSchema4[] @@ -27,6 +33,7 @@ export interface StoryblokTsOptions { customTypeParser?: (key: string, options: JSONSchema4) => void compilerOptions?: CompilerOptions path?: string + resolveLinks?: ResolveLinkOption[] titleSuffix?: string titlePrefix?: string } @@ -34,6 +41,7 @@ export interface StoryblokTsOptions { export interface CliOptions { source: string target?: string + resolveLinks?: string | string[] titleSuffix?: string titlePrefix?: string customTypeParser?: string