Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command for Typescript typedefs generation #79

Merged
merged 36 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3a3f38b
feat: add json-schema-to-typescript dep
Edo-San Jan 17, 2024
4bf878a
feat: add vite and boilerplate ts script
Edo-San Jan 17, 2024
2fc5823
feat: add ts-node for developing and unbuild for building process
Edo-San Jan 22, 2024
87d1a21
feat: add bridge file between cli parser and actual converting function
Edo-San Jan 25, 2024
74623e3
chore: remove package-lock
Edo-San Jan 25, 2024
891af62
feat: update build to ESM and update all imports/exports. Disable typ…
Edo-San Jan 26, 2024
31692c9
feat: update typings and refactor parseBlokSchemaObject function
Edo-San Jan 30, 2024
41704c3
refactor: refactor schemaobject type switch
Edo-San Jan 30, 2024
58f4125
fix: update typeMapper func to correctly generate bloks allowlists
Edo-San Jan 31, 2024
e569a24
fix: fix restrictions on bloks array fields for explicit list of blok…
Edo-San Feb 1, 2024
95c9229
fix: update compile bannercomments and fix usage of custom type parser
Edo-San Feb 2, 2024
bbd7a9b
refactor: move types to dedicated folder
Edo-San Feb 5, 2024
29b9a9a
refactor: refactor types, remove unused cli type
Edo-San Feb 5, 2024
231b758
refactor: update CLI params, refactor types
Edo-San Feb 5, 2024
36973b4
feat: refactor types and add class baseline
Edo-San Feb 7, 2024
c7c26e1
feat: add all methods to TS generation class
Edo-San Feb 7, 2024
6cd5010
refactor: get rid of old pieces of code and update calling new class …
Edo-San Feb 8, 2024
efe2ff6
chore: set all props and methods of the generator class as private
Edo-San Feb 8, 2024
9957fe1
chore: add comments to class methods
Edo-San Feb 8, 2024
10b2e59
chore: improve vars and functions naming consistency
Edo-San Feb 9, 2024
b081154
chore: move all types to typedef file
Edo-San Feb 12, 2024
cebe8c0
feat: add readme basic docs, fix custom type parser path param
Edo-San Feb 12, 2024
a3d3996
feat: update readme
Edo-San Feb 13, 2024
5e858b1
feat: add dev npm script
Edo-San Feb 13, 2024
5718d6c
chore: get rid of unneeded files
Edo-San Feb 13, 2024
c4c344c
chore: update generator class comment
Edo-San Feb 13, 2024
064e1f7
feat: add type module to package json
Edo-San Feb 13, 2024
575f4c0
fix: update tests so that they could run with ESM
Edo-San Feb 22, 2024
21f17c4
fix: prevent tests from writing migration files that caused test errors
Edo-San Feb 22, 2024
25df4c8
feat: add declaration files
Edo-San Feb 26, 2024
08dc253
feat: bump js client dep
Edo-San Feb 28, 2024
37425d7
chore: remove comment from spec file
Edo-San Mar 6, 2024
523db4b
fix: solve conflicts on cli.ts
Edo-San Mar 6, 2024
f7da34b
fix: fix new tests
Edo-San Mar 6, 2024
574a185
feat: add logic to prepend an underscore if a type name starts with a…
Edo-San Mar 11, 2024
c43eb1c
chore: add todo comment to keep in mind edge cases
Edo-San Mar 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module.exports = {
env: {
commonjs: true,
es6: true,
node: true,
'jest/globals': true
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,8 @@ dist

# IntelliJ
.idea/

# CLI generated files
components.*.json
presets.*.json
storyblok-component-types.d.ts
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"editor.formatOnSave": false,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,92 @@ module.exports = function (block) {
}
```

## Typescript
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.

### generate-typescript-typedefs

Generate a file with the type definitions for the specified components' JSON Schemas.

```sh
$ storyblok generate-typescript-typedefs
--sourceFilePaths <PATHS>
--destinationFilePath <PATH>
--typeNamesPrefix <STRING>
--typeNamesSuffix <STRING>
--JSONSchemaToTSOptionsPath <PATH>
--customFieldTypesParserPath <PATH>
```

#### Options

* `sourceFilePaths` <sub>(alias `source`)</sub> : Path(s) to the components JSON file(s) as comma separated values
* `destinationFilePath` <sub>(alias `target`) *optional*</sub> : Path to the Typescript file that will be generated (*default*: `storyblok-component-types.d.ts`)
* `typeNamesPrefix` <sub>(alias `titlePrefix`) *optional*</sub> : A prefix that will be prepended to all the names of the generated types
* `typeNamesSuffix` <sub>(alias `titleSuffix`) *optional*</sub> : A suffix that will be appended to all the names of the generated types (*default*: `Storyblok`)
* `JSONSchemaToTSOptionsPath` <sub>(alias `compilerOptions`) *optional*</sub> : Path to a JSON file with a list of options supported by `json-schema-to-typescript`
* `customFieldTypesParserPath` <sub>(alias `customTypeParser`) *optional*</sub> : Path to the parser file for Custom Field Types

#### Examples

```sh
# Generate typedefs for the components retrieved for the space `12345` via the `storyblok pull-components` command
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json

# Generate typedefs for multiple components sources
$ storyblok generate-typescript-typedefs --sourceFilePaths ./fooComponent-12345.json,./barComponent-12345.json

# Custom path for the typedefs file
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --destinationFilePath ./types/my-custom-type-file.d.ts

# Provide customized options for the JSON-schema-to-typescript lib
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --JSONSchemaToTSOptionsPath ./PathToJSONFileWithCustomOptions.json

# Provide a custom field types parser
$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --customFieldTypesParserPath ./customFieldTypesParser.js

```

##### JSON Schema to Typescript options
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`.

The default values used for the `storyblok generate-typescript-typedefs` command are the same defaults for the library except for two properties:
* `bannerComment` - The default value is `""` to remove noise from the generated Typedefs file
* `unknownAny` - The default value is `false` because it can help a smoother Typescript adoption on a JS project

Example `JSONSchemaToTSOptions` JSON file to remove `additionalProperties` from the generated type definitions:

```json
{
"additionalProperties": false,
}
```

##### Custom Field Types parser
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:
```js
export default function (key, obj) {
switch (obj.field_type) {
case 'my-custom-field-type-name':
return {
[key]: {
type: 'object',
properties: {
color: { type: 'string' }
},
required: ['color']
}
}
default:
return {}
}
}
```





## You're looking for a headstart?

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.
24 changes: 13 additions & 11 deletions __mocks__/fs-extra.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const fs = jest.genMockFromModule('fs-extra')
import { jest } from '@jest/globals'

const fs = jest.createMockFromModule('fs-extra')

let mockFiles = Object.create(null)

Expand All @@ -19,15 +21,15 @@ const readFile = jest.fn((path) => {
mockFiles = path
return Promise.resolve(JSON.stringify([
{
"id": 0,
"full_slug": "another-post",
"content": {
"_uid": "5647c21f-8813-4f8a-ad38-b9f74e0e7c89",
"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.",
"image": "https://a.storyblok.com/f/51376/884x750/3bff01d851/international.svg",
"title": "test",
"category": "news",
"component": "Product"
id: 0,
full_slug: 'another-post',
content: {
_uid: '5647c21f-8813-4f8a-ad38-b9f74e0e7c89',
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.',
image: 'https://a.storyblok.com/f/51376/884x750/3bff01d851/international.svg',
title: 'test',
category: 'news',
component: 'Product'
}
}
]))
Expand Down Expand Up @@ -60,4 +62,4 @@ fs.__clearMockFiles = __clearMockFiles

fs.__setMockFiles = __setMockFiles

module.exports = fs
export default fs
23 changes: 23 additions & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineBuildConfig } from "unbuild";

export default defineBuildConfig({
declaration: true,
rollup: {
inlineDependencies: true,
resolve: {
exportConditions: ["production", "node"] as any,
},
},
entries: ["src/cli"],
externals: [
"@nuxt/test-utils",
"fsevents",
"node:url",
"node:buffer",
"node:path",
"node:child_process",
"node:process",
"node:path",
"node:os",
],
});
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default { transform: {} }
24 changes: 18 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@
"node",
"javascript"
],
"main": "src/cli.js",
"main": "./dist/cli.mjs",
"files": [
"dist/**"
],
"bin": {
"storyblok": "src/cli.js"
"storyblok": "./dist/cli.mjs"
},
"type": "module",
"scripts": {
"build": "unbuild",
"dev": "npm run build && ./dist/cli.mjs",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"test:unit": "jest --silent",
"test:coverage": "jest --coverage"
"test:unit": "node --experimental-vm-modules ./node_modules/.bin/jest --silent",
"test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest --coverage"
},
"author": "Dominik Angerer <dominikangerer1@gmail.com>, Alexander Feiglstorfer <delooks@gmail.com>",
"license": "MIT",
Expand All @@ -36,14 +42,15 @@
"fs-extra": "^9.0.1",
"git-clone": "^0.1.0",
"inquirer": "^7.3.2",
"json-schema-to-typescript": "^13.1.2",
"lodash": "^4.17.21",
"netrc": "0.1.4",
"on-change": "^2.0.1",
"open": "^6.0.0",
"p-series": "^2.1.0",
"path": "^0.12.7",
"simple-uuid": "^0.0.1",
"storyblok-js-client": "^5.14.0",
"storyblok-js-client": "^6.7.1",
"update-notifier": "^5.1.0",
"xml-js": "^1.6.11"
},
Expand All @@ -59,11 +66,16 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"jest": "^26.1.0"
"jest": "^29.7.0",
"typescript": "^5.3.3",
"unbuild": "^2.0.0"
},
"release": {
"branches": [
"master"
]
},
"prettier": {
"printWidth": 120
}
}
64 changes: 50 additions & 14 deletions src/cli.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#!/usr/bin/env node

const commander = require('commander')
import commander from 'commander'
import chalk from 'chalk'
import clear from 'clear'
import figlet from 'figlet'
import inquirer from 'inquirer'
import { ALL_REGIONS, EU_CODE, isRegion } from '@storyblok/region-helper'
import updateNotifier from 'update-notifier'
import fs from 'fs'
import tasks from './tasks'
import { getQuestions, lastStep, api, creds } from './utils'
import { SYNC_TYPES, COMMANDS } from './constants'

const rawPkg = fs.readFileSync('./package.json')
const pkg = JSON.parse(rawPkg)
const program = new commander.Command()

const chalk = require('chalk')
const clear = require('clear')
const figlet = require('figlet')
const inquirer = require('inquirer')
const { ALL_REGIONS, EU_CODE, isRegion } = require('@storyblok/region-helper')

const updateNotifier = require('update-notifier')
const pkg = require('../package.json')

const tasks = require('./tasks')
const { getQuestions, lastStep, api, creds } = require('./utils')
const { SYNC_TYPES, COMMANDS } = require('./constants')
const allRegionsText = ALL_REGIONS.join(', ')

clear()
Expand Down Expand Up @@ -523,6 +523,42 @@ program
}
})

// Generate Typescript type definitions
program
.command(COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS)
// Providing backward-compatible flags with Storyblok Generate TS https://github.com/dohomi/storyblok-generate-ts
.requiredOption('--source, --sourceFilePaths <PATHS>', 'Path(s) to the components JSON file(s) as comma separated values', (paths, _previous) => paths.split(','))
.option('--target, --destinationFilePath <PATH>', 'Path to the Typescript file that will be generated (default: `storyblok-component-types.d.ts`)')
.option('--titlePrefix, --typeNamesPrefix <STRING>', 'A prefix that will be prepended to all the names of the generated types')
.option('--titleSuffix, --typeNamesSuffix <STRING>', 'A suffix that will be appended to all the names of the generated types (*default*: `Storyblok`)')
.option('--compilerOptions, --JSONSchemaToTSOptionsPath <PATH>', 'Path to a JSON file with a list of options supported by json-schema-to-typescript')
.option('--customTypeParser, --customFieldTypesParserPath <PATH>', 'Path to the parser file for Custom Field Types')
.action((options) => {
console.log(`${chalk.blue('-')} Executing ${COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS} task`)

const {
sourceFilePaths,
destinationFilePath,
typeNamesPrefix,
typeNamesSuffix,
customFieldTypesParserPath,
JSONSchemaToTSOptionsPath
} = options

try {
tasks.generateTypescriptTypedefs({
sourceFilePaths,
destinationFilePath,
typeNamesPrefix,
typeNamesSuffix,
customFieldTypesParserPath,
JSONSchemaToTSOptionsPath
})
} catch (e) {
errorHandler(e, COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS)
}
})

program.parse(process.argv)

if (program.rawArgs.length <= 2) {
Expand Down
Loading
Loading