Skip to content

Instantly share code, notes, and snippets.

@eyn
Last active August 26, 2020 09:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save eyn/b9dd0956b2fa6f783b9898b6ffaeb192 to your computer and use it in GitHub Desktop.
Save eyn/b9dd0956b2fa6f783b9898b6ffaeb192 to your computer and use it in GitHub Desktop.
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
const { writeFileSync } = require("fs");
const { compile } = require("json-schema-to-typescript");
const { mapValues, upperFirst, camelCase } = require("lodash");
const prettier = require("prettier");
const StoryblokClient = require("storyblok-js-client");
const interfaceName = (name) => upperFirst(camelCase(name));
if (!process.env.STORYBLOK_TOKEN || !process.env.STORYBLOK_SPACE_ID) {
console.log(
`You must set STORYBLOK_TOKEN and STORYBLOK_SPACE_ID in Environment. To get a token go to: https://app.storyblok.com/#!/me/account`
);
process.exit();
}
const Storyblok = new StoryblokClient({
// eslint-disable-next-line no-undef
oauthToken: process.env.STORYBLOK_TOKEN,
});
const spaceId = process.env.STORYBLOK_SPACE_ID;
async function main() {
const {
data: { component_groups: componentGroups },
} = await Storyblok.get(`spaces/${spaceId}/component_groups/`, {});
const {
data: { components },
} = await Storyblok.get(`spaces/${spaceId}/components/`);
const {
data: { datasources },
} = await Storyblok.get(`spaces/${spaceId}/datasources/`, {});
function getRequired(item) {
return Object.entries(item)
.filter(([_key, value]) => value.required)
.map(([key]) => key);
}
function convertItem(item, _name) {
switch (item.type) {
case "text":
case "textarea":
case "markdown":
return { type: "string" };
case "boolean":
return { type: "boolean" };
case "asset":
return {
type: "string",
tsType: "Asset",
};
case "image":
return {
type: "object",
properties: {
filename: { type: "string" },
},
required: ["filename"],
additionalProperties: false,
};
case "datetime":
return {
type: "string",
};
case "option":
if (!item.source) {
return {
type: "string",
enum: item.options.map((x) => x.value),
};
} else {
if (item.source === "internal_stories") {
return {
type: "string",
tsType: `ContentResult<${interfaceName(item.filter_content_type) || "ContentTypes"}>`,
};
} else if (item.source === "internal") {
return {
type: "string",
tsType: `${interfaceName(item.datasource_slug)}DataSource`,
};
}
throw new Error("UNKNOWN SOURCE" + item.source);
}
case "options":
if (!item.source) {
return {
type: "array",
items: { type: "string", enum: item.options.map((x) => x.value) },
};
} else {
if (item.source === "internal_stories") {
return {
type: "array",
items: {
type: "string",
tsType: `ContentResult<${
interfaceName(item.filter_content_type) || "ContentTypes"
}>`,
},
};
} else if (item.source === "internal") {
return {
type: "array",
items: { type: "string" },
};
}
throw new Error("UNKNOWN SOURCE" + item.source);
}
case "bloks":
if (item.restrict_components) {
if (item.component_whitelist && !item.restrict_type) {
return {
type: "array",
items: {
anyOf: item.component_whitelist.map((component) => ({
type: "object",
tsType: interfaceName(component),
})),
},
};
} else if (item.restrict_type === "groups") {
return {
type: "array",
items: {
anyOf: item.component_group_whitelist.map((componentGroup) => ({
type: "object",
tsType: `${interfaceName(
componentGroups.find((x) => x.uuid === componentGroup).name
)}ComponentGroup`,
})),
},
};
} else {
return { type: "array", items: { type: "object" } };
}
} else {
return { type: "array", items: { type: "object", tsType: "NestableComponents" } };
}
default:
throw new Error("UNKNOWN TYPE" + item.type);
}
}
function generateSchema(schema) {
return [getRequired(schema), mapValues(schema, convertItem)];
}
const definitions = [
`export interface ContentType {
name: string;
created_at: string;
published_at: string;
alternates: [];
id: number;
}`,
`export interface Asset {
alt: string | null;
name: string;
focus: null;
title: string | null;
filename: string;
}`,
`export interface ContentResult<T> {
alternates: [];
content: T;
created_at: string;
default_full_slug: string | null;
first_published_at: string;
full_slug: string;
group_id: string;
id: number;
is_startpage: boolean;
lang: string;
meta_data: null;
name: string;
parent_id: number;
path: null;
position: number;
published_at: string;
release_id: null;
slug: string;
sort_by_date: null;
tag_list: string[];
translated_slugs: string[];
uuid: string;
}`,
];
for (const component of components) {
const [required, properties] = generateSchema(component.schema);
const jsonSchema = {
title: component.name,
type: "object",
properties,
additionalProperties: false,
required,
};
if (component.is_nestable) {
jsonSchema.properties.component = {
type: "string",
enum: [component.name],
};
jsonSchema.required.push("component");
}
jsonSchema.properties._uid = {
type: "string",
};
jsonSchema.required.push("_uid");
const result = await compile(jsonSchema, jsonSchema.title, {
declareExternallyReferenced: true,
bannerComment: "",
});
definitions.push(`${result};\n`);
}
const nestableComponents = components
.filter((x) => x.is_nestable)
.map((x) => interfaceName(x.name));
definitions.push(`export type NestableComponents = ${nestableComponents.join(" | ")};`);
const contentTypes = components.filter((x) => x.is_root).map((x) => interfaceName(x.name));
definitions.push(`export type ContentTypes = ${contentTypes.join(" | ")};`);
for (const datasource of datasources) {
const entries = await Storyblok.get(`spaces/${spaceId}/datasource_entries/`, {
datasource_id: datasource.id,
});
definitions.push(
`export enum ${interfaceName(
datasource.name
)}DataSource { ${entries.data.datasource_entries
.map((x) => `${x.value} = "${x.value}"`)
.join(",")}};\n`
);
}
for (const group of componentGroups) {
const groupComponents = components
.filter((x) => x.component_group_uuid === group.uuid)
.map((x) => interfaceName(x.name));
definitions.push(
`export type ${interfaceName(group.name)}ComponentGroup = ${groupComponents.join(" | ")}`
);
}
writeFileSync(
"./schema/index.ts",
prettier.format(definitions.join("\n\n"), {
parser: "typescript",
})
);
}
main();
export interface ContentType {
name: string;
created_at: string;
published_at: string;
alternates: [];
id: number;
}
export interface Asset {
alt: string | null;
name: string;
focus: null;
title: string | null;
filename: string;
}
export interface ContentResult<T> {
alternates: [];
content: T;
created_at: string;
default_full_slug: string | null;
first_published_at: string;
full_slug: string;
group_id: string;
id: number;
is_startpage: boolean;
lang: string;
meta_data: null;
name: string;
parent_id: number;
path: null;
position: number;
published_at: string;
release_id: null;
slug: string;
sort_by_date: null;
tag_list: string[];
translated_slugs: string[];
uuid: string;
}
export interface Author {
name?: string;
picture?: Asset;
role?: string;
_uid: string;
}
export interface BlogRoll {
variant: "two_rows_featured_post" | "hero_post" | "single_row";
category?: ContentResult<Category>;
tags?: ContentResult<Tag>[];
component: "blog_roll";
_uid: string;
}
export interface Bullets {
bullets: string;
icon?: IconsDataSource;
iconColor?: ColorsDataSource;
component: "bullets";
_uid: string;
}
export interface Category {
_uid: string;
}
export interface Faq {
answer?: string;
_uid: string;
}
export interface FaqsList {
show_titles?: boolean;
title?: string;
subtitle?: string;
category_uuid?: ContentResult<Category>;
tag_uuids?: ContentResult<Tag>[];
component: "faqs_list";
_uid: string;
}
export interface HeaderAndText {
header: string;
text: string;
component: "header_and_text";
_uid: string;
}
export interface ImportedHtml {
content?: string;
component: "imported_html";
_uid: string;
}
export interface Page {
body?: PageItemsComponentGroup[];
_uid: string;
}
export interface PageHeaderLarge {
title: string;
subtitle: string;
background?: Asset;
backgroundColor?: ColorsDataSource;
titleColor?: ColorsDataSource;
component: "page_header_large";
_uid: string;
}
export interface PageHeaderSmall {
title: string;
subtitle?: string;
backgroundColor?: ColorsDataSource;
component: "page_header_small";
_uid: string;
}
export interface Partner {
logo: Asset;
_uid: string;
}
export interface PartnersList {
title?: string;
link?: boolean;
partners?: ContentResult<Partner>[];
component: "partners_list";
_uid: string;
}
export interface Post {
title?: string;
excerpt?: string;
cover_image?: Asset;
date?: string;
author?: ContentResult<Author>;
category?: ContentResult<Category>;
tags?: ContentResult<Tag>[];
content?: NestableComponents[];
_uid: string;
}
export interface ProcessCardGrid {
preTitle?: string;
title?: string;
items?: ProcessCardItem[];
component: "process_card_grid";
_uid: string;
}
export interface ProcessCardItem {
title?: string;
description?: string;
component: "process_card_item";
_uid: string;
}
export interface ProfileCard {
component: "profile_card";
_uid: string;
}
export interface ProfilesPreview {
component: "profiles_preview";
_uid: string;
}
export interface Quote {
title?: string;
quote?: string;
who?: string;
component: "quote";
_uid: string;
}
export interface SideBySideImageAndContent {
image?: Asset;
content: (Bullets | HeaderAndText)[];
image_position?: "left" | "right" | "";
new_image?: {
filename: string;
};
component: "side_by_side_image_and_content";
_uid: string;
}
export interface Tab {
label?: string;
content: NestableComponents[];
component: "tab";
_uid: string;
}
export interface Tabs {
items?: Tab[];
style?: "default" | "fullWidth" | "centered";
component: "tabs";
_uid: string;
}
export interface Tag {
_uid: string;
}
export interface UspGrid {
items?: UspItem[];
component: "usp_grid";
_uid: string;
}
export interface UspItem {
title?: string;
icon?: IconsDataSource;
description?: string;
component: "usp_item";
_uid: string;
}
export type NestableComponents =
| BlogRoll
| Bullets
| FaqsList
| HeaderAndText
| ImportedHtml
| PageHeaderLarge
| PageHeaderSmall
| PartnersList
| ProcessCardGrid
| ProcessCardItem
| ProfileCard
| ProfilesPreview
| Quote
| SideBySideImageAndContent
| Tab
| Tabs
| UspGrid
| UspItem;
export type ContentTypes =
| Author
| Category
| Faq
| Page
| Partner
| Post
| Tag;
export enum IconsDataSource {
LockOpen = "LockOpen",
PersonOutline = "PersonOutline",
Star = "Star",
PersonAddOutlined = "PersonAddOutlined",
ScheduleOutlined = "ScheduleOutlined",
CallOutlined = "CallOutlined",
}
export enum ColorsDataSource {
default = "default",
charcoal = "charcoal",
honey = "honey",
kale = "kale",
mint = "mint",
salmon = "salmon",
spinach = "spinach",
}
export type BlogItemsComponentGroup = ImportedHtml;
export type PageItemsComponentGroup =
| BlogRoll
| FaqsList
| PageHeaderLarge
| PageHeaderSmall
| PartnersList
| ProcessCardGrid
| ProcessCardItem
| ProfileCard
| ProfilesPreview
| Quote
| SideBySideImageAndContent
| Tab
| Tabs
| UspGrid;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment