Skip to content
This repository was archived by the owner on Jan 19, 2026. It is now read-only.

Commit 3123308

Browse files
authored
Merge pull request #79 from storyblok/WEB-790
Add command for Typescript typedefs generation
2 parents fb87d49 + c43eb1c commit 3123308

73 files changed

Lines changed: 5100 additions & 2886 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
module.exports = {
22
env: {
3-
commonjs: true,
43
es6: true,
54
node: true,
65
'jest/globals': true

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,8 @@ dist
107107

108108
# IntelliJ
109109
.idea/
110+
111+
# CLI generated files
112+
components.*.json
113+
presets.*.json
114+
storyblok-component-types.d.ts

.vscode/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"editor.formatOnSave": false,
3+
"[typescript]": {
4+
"editor.defaultFormatter": "esbenp.prettier-vscode",
5+
"editor.formatOnSave": true,
6+
},
7+
"editor.defaultFormatter": "esbenp.prettier-vscode"
8+
}

README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,92 @@ module.exports = function (block) {
554554
}
555555
```
556556

557+
## Typescript
558+
It is possible to generate Typescript type definitions for your Storyblok components. The type definitions are based on the components' JSON Schema that can be retrieved with the [pull-components](#pull-components) command.
559+
560+
### generate-typescript-typedefs
561+
562+
Generate a file with the type definitions for the specified components' JSON Schemas.
563+
564+
```sh
565+
$ storyblok generate-typescript-typedefs
566+
--sourceFilePaths <PATHS>
567+
--destinationFilePath <PATH>
568+
--typeNamesPrefix <STRING>
569+
--typeNamesSuffix <STRING>
570+
--JSONSchemaToTSOptionsPath <PATH>
571+
--customFieldTypesParserPath <PATH>
572+
```
573+
574+
#### Options
575+
576+
* `sourceFilePaths` <sub>(alias `source`)</sub> : Path(s) to the components JSON file(s) as comma separated values
577+
* `destinationFilePath` <sub>(alias `target`) *optional*</sub> : Path to the Typescript file that will be generated (*default*: `storyblok-component-types.d.ts`)
578+
* `typeNamesPrefix` <sub>(alias `titlePrefix`) *optional*</sub> : A prefix that will be prepended to all the names of the generated types
579+
* `typeNamesSuffix` <sub>(alias `titleSuffix`) *optional*</sub> : A suffix that will be appended to all the names of the generated types (*default*: `Storyblok`)
580+
* `JSONSchemaToTSOptionsPath` <sub>(alias `compilerOptions`) *optional*</sub> : Path to a JSON file with a list of options supported by `json-schema-to-typescript`
581+
* `customFieldTypesParserPath` <sub>(alias `customTypeParser`) *optional*</sub> : Path to the parser file for Custom Field Types
582+
583+
#### Examples
584+
585+
```sh
586+
# Generate typedefs for the components retrieved for the space `12345` via the `storyblok pull-components` command
587+
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json
588+
589+
# Generate typedefs for multiple components sources
590+
$ storyblok generate-typescript-typedefs --sourceFilePaths ./fooComponent-12345.json,./barComponent-12345.json
591+
592+
# Custom path for the typedefs file
593+
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --destinationFilePath ./types/my-custom-type-file.d.ts
594+
595+
# Provide customized options for the JSON-schema-to-typescript lib
596+
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --JSONSchemaToTSOptionsPath ./PathToJSONFileWithCustomOptions.json
597+
598+
# Provide a custom field types parser
599+
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --customFieldTypesParserPath ./customFieldTypesParser.js
600+
601+
```
602+
603+
##### JSON Schema to Typescript options
604+
This script uses the `json-schema-to-typescript` library under the hood. Values of the [JSON Schema to Typescript options](https://www.npmjs.com/package/json-schema-to-typescript#options) can be customized providing a JSON file to the `JSONSchemaToTSOptionsPath`.
605+
606+
The default values used for the `storyblok generate-typescript-typedefs` command are the same defaults for the library except for two properties:
607+
* `bannerComment` - The default value is `""` to remove noise from the generated Typedefs file
608+
* `unknownAny` - The default value is `false` because it can help a smoother Typescript adoption on a JS project
609+
610+
Example `JSONSchemaToTSOptions` JSON file to remove `additionalProperties` from the generated type definitions:
611+
612+
```json
613+
{
614+
"additionalProperties": false,
615+
}
616+
```
617+
618+
##### Custom Field Types parser
619+
Storyblok [Custom Field Types](https://www.storyblok.com/docs/plugins/field-plugins/introduction) do not have inherent JSONSchema definitions. To overcome this issue, you can provide a path to a script exporting a parser function that should render a [JSONSchema Node](https://json-schema.org/learn/getting-started-step-by-step#define-properties) for each of your Custom Field Types. The parser function should be exported as a default export, like in the following example:
620+
```js
621+
export default function (key, obj) {
622+
switch (obj.field_type) {
623+
case 'my-custom-field-type-name':
624+
return {
625+
[key]: {
626+
type: 'object',
627+
properties: {
628+
color: { type: 'string' }
629+
},
630+
required: ['color']
631+
}
632+
}
633+
default:
634+
return {}
635+
}
636+
}
637+
```
638+
639+
640+
641+
642+
557643
## You're looking for a headstart?
558644

559645
Check out our guides for client side apps (VueJS, Angular, React, ...), static site (Jekyll, NuxtJs, ...), dynamic site examples (Node, PHP, Python, Laravel, ...) on our [Getting Started](https://www.storyblok.com/getting-started) page.

__mocks__/fs-extra.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const fs = jest.genMockFromModule('fs-extra')
1+
import { jest } from '@jest/globals'
2+
3+
const fs = jest.createMockFromModule('fs-extra')
24

35
let mockFiles = Object.create(null)
46

@@ -19,15 +21,15 @@ const readFile = jest.fn((path) => {
1921
mockFiles = path
2022
return Promise.resolve(JSON.stringify([
2123
{
22-
"id": 0,
23-
"full_slug": "another-post",
24-
"content": {
25-
"_uid": "5647c21f-8813-4f8a-ad38-b9f74e0e7c89",
26-
"text": "Donec tortor mauris, mollis vel pretium vitae, lacinia nec sapien. Donec erat neque, ullamcorper tincidunt iaculis sit amet, pharetra bibendum ipsum. Nunc mattis risus ac ante consequat nec pulvinar neque molestie. Etiam interdum nunc at metus lacinia non varius erat dignissim. Integer elementum, felis id facilisis vulputate, ipsum tellus venenatis dui, at blandit nibh massa in dolor. Cras a ultricies sapien. Vivamus adipiscing feugiat pharetra.",
27-
"image": "https://a.storyblok.com/f/51376/884x750/3bff01d851/international.svg",
28-
"title": "test",
29-
"category": "news",
30-
"component": "Product"
24+
id: 0,
25+
full_slug: 'another-post',
26+
content: {
27+
_uid: '5647c21f-8813-4f8a-ad38-b9f74e0e7c89',
28+
text: 'Donec tortor mauris, mollis vel pretium vitae, lacinia nec sapien. Donec erat neque, ullamcorper tincidunt iaculis sit amet, pharetra bibendum ipsum. Nunc mattis risus ac ante consequat nec pulvinar neque molestie. Etiam interdum nunc at metus lacinia non varius erat dignissim. Integer elementum, felis id facilisis vulputate, ipsum tellus venenatis dui, at blandit nibh massa in dolor. Cras a ultricies sapien. Vivamus adipiscing feugiat pharetra.',
29+
image: 'https://a.storyblok.com/f/51376/884x750/3bff01d851/international.svg',
30+
title: 'test',
31+
category: 'news',
32+
component: 'Product'
3133
}
3234
}
3335
]))
@@ -60,4 +62,4 @@ fs.__clearMockFiles = __clearMockFiles
6062

6163
fs.__setMockFiles = __setMockFiles
6264

63-
module.exports = fs
65+
export default fs

build.config.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { defineBuildConfig } from "unbuild";
2+
3+
export default defineBuildConfig({
4+
declaration: true,
5+
rollup: {
6+
inlineDependencies: true,
7+
resolve: {
8+
exportConditions: ["production", "node"] as any,
9+
},
10+
},
11+
entries: ["src/cli"],
12+
externals: [
13+
"@nuxt/test-utils",
14+
"fsevents",
15+
"node:url",
16+
"node:buffer",
17+
"node:path",
18+
"node:child_process",
19+
"node:process",
20+
"node:path",
21+
"node:os",
22+
],
23+
});

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default { transform: {} }

package.json

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@
1212
"node",
1313
"javascript"
1414
],
15-
"main": "src/cli.js",
15+
"main": "./dist/cli.mjs",
16+
"files": [
17+
"dist/**"
18+
],
1619
"bin": {
17-
"storyblok": "src/cli.js"
20+
"storyblok": "./dist/cli.mjs"
1821
},
22+
"type": "module",
1923
"scripts": {
24+
"build": "unbuild",
25+
"dev": "npm run build && ./dist/cli.mjs",
2026
"lint": "eslint src/",
2127
"lint:fix": "eslint src/ --fix",
22-
"test:unit": "jest --silent",
23-
"test:coverage": "jest --coverage"
28+
"test:unit": "node --experimental-vm-modules ./node_modules/.bin/jest --silent",
29+
"test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest --coverage"
2430
},
2531
"author": "Dominik Angerer <dominikangerer1@gmail.com>, Alexander Feiglstorfer <delooks@gmail.com>",
2632
"license": "MIT",
@@ -36,14 +42,15 @@
3642
"fs-extra": "^9.0.1",
3743
"git-clone": "^0.1.0",
3844
"inquirer": "^7.3.2",
45+
"json-schema-to-typescript": "^13.1.2",
3946
"lodash": "^4.17.21",
4047
"netrc": "0.1.4",
4148
"on-change": "^2.0.1",
4249
"open": "^6.0.0",
4350
"p-series": "^2.1.0",
4451
"path": "^0.12.7",
4552
"simple-uuid": "^0.0.1",
46-
"storyblok-js-client": "^5.14.0",
53+
"storyblok-js-client": "^6.7.1",
4754
"update-notifier": "^5.1.0",
4855
"xml-js": "^1.6.11"
4956
},
@@ -59,11 +66,16 @@
5966
"eslint-plugin-node": "^11.1.0",
6067
"eslint-plugin-promise": "^4.2.1",
6168
"eslint-plugin-standard": "^4.0.1",
62-
"jest": "^26.1.0"
69+
"jest": "^29.7.0",
70+
"typescript": "^5.3.3",
71+
"unbuild": "^2.0.0"
6372
},
6473
"release": {
6574
"branches": [
6675
"master"
6776
]
77+
},
78+
"prettier": {
79+
"printWidth": 120
6880
}
6981
}

src/cli.js

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
#!/usr/bin/env node
22

3-
const commander = require('commander')
3+
import commander from 'commander'
4+
import chalk from 'chalk'
5+
import clear from 'clear'
6+
import figlet from 'figlet'
7+
import inquirer from 'inquirer'
8+
import { ALL_REGIONS, EU_CODE, isRegion } from '@storyblok/region-helper'
9+
import updateNotifier from 'update-notifier'
10+
import fs from 'fs'
11+
import tasks from './tasks'
12+
import { getQuestions, lastStep, api, creds } from './utils'
13+
import { SYNC_TYPES, COMMANDS } from './constants'
14+
15+
const rawPkg = fs.readFileSync('./package.json')
16+
const pkg = JSON.parse(rawPkg)
417
const program = new commander.Command()
5-
6-
const chalk = require('chalk')
7-
const clear = require('clear')
8-
const figlet = require('figlet')
9-
const inquirer = require('inquirer')
10-
const { ALL_REGIONS, EU_CODE, isRegion } = require('@storyblok/region-helper')
11-
12-
const updateNotifier = require('update-notifier')
13-
const pkg = require('../package.json')
14-
15-
const tasks = require('./tasks')
16-
const { getQuestions, lastStep, api, creds } = require('./utils')
17-
const { SYNC_TYPES, COMMANDS } = require('./constants')
1818
const allRegionsText = ALL_REGIONS.join(', ')
1919

2020
clear()
@@ -523,6 +523,42 @@ program
523523
}
524524
})
525525

526+
// Generate Typescript type definitions
527+
program
528+
.command(COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS)
529+
// Providing backward-compatible flags with Storyblok Generate TS https://github.com/dohomi/storyblok-generate-ts
530+
.requiredOption('--source, --sourceFilePaths <PATHS>', 'Path(s) to the components JSON file(s) as comma separated values', (paths, _previous) => paths.split(','))
531+
.option('--target, --destinationFilePath <PATH>', 'Path to the Typescript file that will be generated (default: `storyblok-component-types.d.ts`)')
532+
.option('--titlePrefix, --typeNamesPrefix <STRING>', 'A prefix that will be prepended to all the names of the generated types')
533+
.option('--titleSuffix, --typeNamesSuffix <STRING>', 'A suffix that will be appended to all the names of the generated types (*default*: `Storyblok`)')
534+
.option('--compilerOptions, --JSONSchemaToTSOptionsPath <PATH>', 'Path to a JSON file with a list of options supported by json-schema-to-typescript')
535+
.option('--customTypeParser, --customFieldTypesParserPath <PATH>', 'Path to the parser file for Custom Field Types')
536+
.action((options) => {
537+
console.log(`${chalk.blue('-')} Executing ${COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS} task`)
538+
539+
const {
540+
sourceFilePaths,
541+
destinationFilePath,
542+
typeNamesPrefix,
543+
typeNamesSuffix,
544+
customFieldTypesParserPath,
545+
JSONSchemaToTSOptionsPath
546+
} = options
547+
548+
try {
549+
tasks.generateTypescriptTypedefs({
550+
sourceFilePaths,
551+
destinationFilePath,
552+
typeNamesPrefix,
553+
typeNamesSuffix,
554+
customFieldTypesParserPath,
555+
JSONSchemaToTSOptionsPath
556+
})
557+
} catch (e) {
558+
errorHandler(e, COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS)
559+
}
560+
})
561+
526562
program.parse(process.argv)
527563

528564
if (program.rawArgs.length <= 2) {

0 commit comments

Comments
 (0)