@mattgrill/storyblok-11ty 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Configuration options for StoryblokTo11tyData
3
+ */
4
+ export interface StoryblokTo11tyConfig {
5
+ /** The API token of the Storyblok space */
6
+ token?: string;
7
+ /** The version of the API to fetch (draft or published) */
8
+ version?: 'draft' | 'published';
9
+ /** The path to the layouts folder in 11ty */
10
+ layouts_path?: string;
11
+ /** The path where to store the entries */
12
+ stories_path?: string;
13
+ /** The path where to store the datasources */
14
+ datasources_path?: string;
15
+ /** An object with parameter -> value to match specific component to specific layouts */
16
+ components_layouts_map?: Record<string, string>;
17
+ /** The config for the Storyblok JS client */
18
+ storyblok_client_config?: StoryblokClientConfig;
19
+ }
20
+ /**
21
+ * Storyblok client configuration
22
+ */
23
+ export interface StoryblokClientConfig {
24
+ accessToken?: string;
25
+ cache?: {
26
+ clear?: 'auto' | 'manual';
27
+ type?: 'memory' | 'none';
28
+ };
29
+ [key: string]: unknown;
30
+ }
31
+ /**
32
+ * Story data structure from Storyblok API
33
+ */
34
+ export interface Story {
35
+ uuid: string;
36
+ full_slug: string;
37
+ path?: string;
38
+ content: {
39
+ component: string;
40
+ [key: string]: unknown;
41
+ };
42
+ [key: string]: unknown;
43
+ }
44
+ /**
45
+ * Transformed story with 11ty metadata
46
+ */
47
+ export interface TransformedStory extends Omit<Story, 'content'> {
48
+ layout: string;
49
+ tags: string;
50
+ data: Story['content'];
51
+ permalink: string;
52
+ }
53
+ /**
54
+ * Datasource entry from Storyblok
55
+ */
56
+ export interface DatasourceEntry {
57
+ id: number;
58
+ name: string;
59
+ value: string;
60
+ dimension_value: string | null;
61
+ [key: string]: unknown;
62
+ }
63
+ /**
64
+ * Datasource information
65
+ */
66
+ export interface Datasource {
67
+ id: number;
68
+ name: string;
69
+ slug: string;
70
+ dimensions?: Array<{
71
+ id: number;
72
+ name: string;
73
+ entry_value: string;
74
+ }>;
75
+ [key: string]: unknown;
76
+ }
77
+ /**
78
+ * Parameters for fetching stories
79
+ */
80
+ export interface GetStoriesParams {
81
+ /** Filter by component name */
82
+ component?: string;
83
+ /** Resolve relations (comma-separated component.field_name values) */
84
+ resolve_relations?: string;
85
+ /** Resolve links */
86
+ resolve_links?: string;
87
+ /** Language code */
88
+ language?: string;
89
+ /** Fallback language code */
90
+ fallback_lang?: string;
91
+ }
92
+ /**
93
+ * API response wrapper
94
+ */
95
+ export interface ApiResponse<T = unknown> {
96
+ data?: T;
97
+ error?: boolean;
98
+ message?: unknown;
99
+ }
100
+ /**
101
+ * Storyblok API response structure
102
+ */
103
+ export interface StoryblokApiResponse<T = unknown> {
104
+ data: {
105
+ [key: string]: T;
106
+ };
107
+ headers: {
108
+ total: number;
109
+ [key: string]: unknown;
110
+ };
111
+ }
112
+ /**
113
+ * Request options for API calls
114
+ */
115
+ export interface RequestOptions {
116
+ query?: Record<string, string | number>;
117
+ }
118
+ /**
119
+ * Plugin configuration options
120
+ */
121
+ export interface PluginConfig {
122
+ /** The folder containing the templates of the blocks */
123
+ blocks_folder?: string;
124
+ }
125
+ /**
126
+ * Eleventy configuration object
127
+ */
128
+ export interface EleventyConfig {
129
+ addLiquidTag: (name: string, callback: (engine: unknown) => unknown) => void;
130
+ addNunjucksTag: (name: string, callback: (engine: unknown, env: unknown) => unknown) => void;
131
+ [key: string]: unknown;
132
+ }
133
+ /**
134
+ * Rich text data structure
135
+ */
136
+ export interface RichTextData {
137
+ content?: Array<unknown>;
138
+ [key: string]: unknown;
139
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Utility class for common operations
3
+ */
4
+ export declare class Utils {
5
+ /**
6
+ * Convert text to URL-friendly slug
7
+ * @param text - The text to slugify
8
+ * @returns Slugified string
9
+ */
10
+ static slugify(text: string): string;
11
+ }
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@mattgrill/storyblok-11ty",
3
+ "version": "2.0.0",
4
+ "description": "Import Stories and Datasources from Storyblok to 11ty as data objects or static files and it adds custom tags for blocks parsing.",
5
+ "type": "commonjs",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "rspack build --mode production && tsc --declaration --emitDeclarationOnly --outDir dist",
22
+ "build:watch": "rspack build --mode development --watch",
23
+ "type-check": "tsc --noEmit",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage",
27
+ "lint": "eslint src/**/*.ts",
28
+ "lint:fix": "eslint src/**/*.ts --fix",
29
+ "format": "prettier --write \"src/**/*.ts\"",
30
+ "format:check": "prettier --check \"src/**/*.ts\"",
31
+ "clean": "rm -rf dist",
32
+ "prebuild": "npm run clean && npm run type-check",
33
+ "prepublishOnly": "npm run build && npm test"
34
+ },
35
+ "keywords": [
36
+ "Storyblok",
37
+ "11ty",
38
+ "eleventy",
39
+ "static-site",
40
+ "cms",
41
+ "typescript"
42
+ ],
43
+ "deprecated": false,
44
+ "author": {
45
+ "name": "Christian Zoppi",
46
+ "email": "me@christianzoppi.com"
47
+ },
48
+ "license": "MIT",
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "dependencies": {
53
+ "@storyblok/richtext": "^3.8.2",
54
+ "storyblok-js-client": "^7.2.2"
55
+ },
56
+ "devDependencies": {
57
+ "@rspack/cli": "^1.1.8",
58
+ "@rspack/core": "^1.1.8",
59
+ "@types/node": "^22.10.5",
60
+ "@typescript-eslint/eslint-plugin": "^8.19.1",
61
+ "@typescript-eslint/parser": "^8.19.1",
62
+ "@vitest/coverage-v8": "^3.0.4",
63
+ "eslint": "^9.17.0",
64
+ "eslint-config-prettier": "^9.1.0",
65
+ "eslint-plugin-prettier": "^5.2.1",
66
+ "husky": "^9.1.7",
67
+ "lint-staged": "^15.2.11",
68
+ "prettier": "^3.4.2",
69
+ "typescript": "^5.7.2",
70
+ "vitest": "^3.0.4"
71
+ },
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "git+https://github.com/christianzoppi/storyblok-11ty"
75
+ },
76
+ "bugs": {
77
+ "url": "https://github.com/christianzoppi/storyblok-11ty/issues"
78
+ },
79
+ "lint-staged": {
80
+ "*.ts": [
81
+ "eslint --fix",
82
+ "prettier --write"
83
+ ]
84
+ },
85
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
86
+ }
package/readme.md ADDED
@@ -0,0 +1,419 @@
1
+ # Storyblok to 11ty
2
+
3
+ The package aims at helping you fetch all the storyblok content in order to use it in 11ty or in any SSG. The package is framework agnostic, hence you could use it with any framework or library. You won't be locked to 11ty and you'd be able to switch to other ssg or framework if needed.
4
+
5
+ The package contains two classes:
6
+ - **StoryblokTo11tyData**. It can be used to fetch content or datasources from storyblok and store them as `json` files.
7
+ - **StoryblokTo11tyPlugin**. An 11ty plugin. It gives you the opportunity to use custom tags for *blocks* and *richtexts* fields.
8
+
9
+ ## Installation and Setup
10
+
11
+ ```bash
12
+ npm install --save storyblok-11ty
13
+ # or
14
+ yarn add storyblok-11ty
15
+ ```
16
+
17
+ ## Version 2.0 Breaking Changes
18
+
19
+ Version 2.0 is a complete rewrite in TypeScript with several improvements:
20
+ - **TypeScript Support**: Full type definitions included
21
+ - **Named Exports**: Classes are now exported as `StoryblokTo11tyData` and `StoryblokTo11tyPlugin`
22
+ - **Modern Build System**: Built with Rspack for better performance
23
+ - **ESM & CommonJS**: Supports both module systems
24
+
25
+ ### Migration from 1.x
26
+
27
+ **Before (1.x):**
28
+ ```javascript
29
+ const StoryblokTo11ty = require('storyblok-11ty');
30
+ const sb = new StoryblokTo11ty.importer({token: 'your-token'});
31
+ ```
32
+
33
+ **After (2.x):**
34
+ ```javascript
35
+ // CommonJS
36
+ const { StoryblokTo11tyData } = require('storyblok-11ty');
37
+ const sb = new StoryblokTo11tyData({token: 'your-token'});
38
+
39
+ // ESM / TypeScript
40
+ import { StoryblokTo11tyData } from 'storyblok-11ty';
41
+ const sb = new StoryblokTo11tyData({token: 'your-token'});
42
+ ```
43
+
44
+ ## TypeScript Support
45
+
46
+ All types are exported and available for use:
47
+
48
+ ```typescript
49
+ import {
50
+ StoryblokTo11tyData,
51
+ StoryblokTo11tyPlugin,
52
+ StoryblokTo11tyConfig,
53
+ Story,
54
+ TransformedStory,
55
+ DatasourceEntry
56
+ } from 'storyblok-11ty';
57
+
58
+ const config: StoryblokTo11tyConfig = {
59
+ token: 'your-space-token',
60
+ version: 'published'
61
+ };
62
+
63
+ const sb = new StoryblokTo11tyData(config);
64
+ ```
65
+
66
+ ## Data Fetching
67
+
68
+ ### Class `StoryblokTo11tyData`
69
+
70
+ **Parameters**
71
+
72
+ - `config` Object (StoryblokTo11tyConfig)
73
+ - `token` String, required. Your Storyblok access token. Check [here](https://www.storyblok.com/docs/api/content-delivery#topics/authentication) how to get it.
74
+ - `version` String, optional. Possible values are `published` or `draft`. Default is `draft`.
75
+ - `content_path` String, optional. The directory path where you want to store content files. Default is `_data`.
76
+ - `datasources_path` String, optional. The directory path where you want to store datasources files. Default is `_data`.
77
+
78
+ **Examples**
79
+
80
+ **TypeScript:**
81
+ ```typescript
82
+ // Example of Global Data File in the _data directory
83
+ import { StoryblokTo11tyData, type StoryblokTo11tyConfig } from 'storyblok-11ty';
84
+
85
+ export default async () => {
86
+ const config: StoryblokTo11tyConfig = {
87
+ token: 'your-space-token',
88
+ version: 'published'
89
+ };
90
+ const sb = new StoryblokTo11tyData(config);
91
+
92
+ // Return all the stories from storyblok
93
+ return await sb.getStories();
94
+ }
95
+ ```
96
+
97
+ **JavaScript (CommonJS):**
98
+ ```javascript
99
+ const { StoryblokTo11tyData } = require('storyblok-11ty');
100
+
101
+ module.exports = async () => {
102
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
103
+
104
+ // Return all the stories from storyblok
105
+ return await sb.getStories();
106
+ }
107
+ ```
108
+
109
+ ### Method `getStories`
110
+
111
+ With this method you can get all the Stories or a single one or a subset of them. The stories will be returned in a javascript object already parsed.
112
+
113
+ **Parameters**
114
+ - `[slug]` String, optional. The slug of the story or a storyblok-js-client request query, like `/stories?starts_with=blog`
115
+ - `[options]` Object, optional. It allows to pass additional parameters to the `storyblok-js-client` `getStories` call.
116
+
117
+ **Return**
118
+ Promise. The response of the promise is the data received from Storyblok. In case you are getting only one story, the structure of the object will be:
119
+
120
+ ```typescript
121
+ {
122
+ total: 1,
123
+ stories: Story[]
124
+ }
125
+ ```
126
+ Otherwise it will be:
127
+ ```typescript
128
+ Array<{
129
+ total: number
130
+ stories: Story[]
131
+ }>
132
+ ```
133
+
134
+ **Examples**
135
+
136
+ **TypeScript:**
137
+ ```typescript
138
+ import { StoryblokTo11tyData, type Story } from 'storyblok-11ty';
139
+
140
+ export default async () => {
141
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
142
+
143
+ const result = await sb.getStories();
144
+ return result;
145
+ }
146
+ ```
147
+
148
+ **JavaScript:**
149
+ ```javascript
150
+ const { StoryblokTo11tyData } = require('storyblok-11ty');
151
+
152
+ module.exports = async () => {
153
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
154
+
155
+ return await sb.getStories();
156
+ }
157
+ ```
158
+
159
+ ### Method `storeStories`
160
+
161
+ Store all the stories or a subset of them. The stories will be stored as `json` files in the `_data` folder or in the one specified through the `content_path` parameter of the `StoryblokTo11tyData` instance. Each story will be stored in a file with its slug.
162
+
163
+ **Parameters**
164
+ - `[slug]` String, optional. The slug of the story or a storyblok-js-client request query, like `/stories?starts_with=blog`
165
+
166
+ **Return**
167
+ Promise. Return `false` if something went wrong in the process, otherwise `true`.
168
+
169
+ **Examples**
170
+
171
+ **TypeScript:**
172
+ ```typescript
173
+ import { StoryblokTo11tyData } from 'storyblok-11ty';
174
+
175
+ const sb = new StoryblokTo11tyData({
176
+ token: 'your-space-token',
177
+ content_path: '_data/stories'
178
+ });
179
+
180
+ // Store all stories
181
+ await sb.storeStories();
182
+
183
+ // Store only the home story
184
+ await sb.storeStories('home');
185
+ ```
186
+
187
+ **JavaScript:**
188
+ ```javascript
189
+ const { StoryblokTo11tyData } = require('storyblok-11ty');
190
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
191
+
192
+ // Store all stories
193
+ await sb.storeStories();
194
+
195
+ // Store only the home story
196
+ await sb.storeStories('home');
197
+ ```
198
+
199
+ ### Method `getDatasources`
200
+
201
+ With this method you can get all the datasources or one in particular. The datasources will be returned in a javascript object already parsed.
202
+
203
+ **Parameters**
204
+ - `[datasource_slug]` String, optional. The slug of the datasource you want to retrieve.
205
+
206
+ **Return**
207
+ Promise. The response of the promise is an object with all the datasources or an array of entries in case you are requesting a single datasource.
208
+
209
+ **Examples**
210
+
211
+ **TypeScript:**
212
+ ```typescript
213
+ import { StoryblokTo11tyData, type DatasourceEntry } from 'storyblok-11ty';
214
+
215
+ // Get all datasources
216
+ export const allDatasources = async () => {
217
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
218
+ return await sb.getDatasources();
219
+ }
220
+
221
+ // Get specific datasource
222
+ export const categories = async (): Promise<DatasourceEntry[]> => {
223
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
224
+ return await sb.getDatasources('categories') as DatasourceEntry[];
225
+ }
226
+ ```
227
+
228
+ **JavaScript:**
229
+ ```javascript
230
+ const { StoryblokTo11tyData } = require('storyblok-11ty');
231
+
232
+ // Get all datasources
233
+ module.exports = async () => {
234
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
235
+ return await sb.getDatasources();
236
+ }
237
+
238
+ // Get specific datasource (categories)
239
+ module.exports = async () => {
240
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
241
+ return await sb.getDatasources('categories');
242
+ }
243
+ ```
244
+
245
+ ### Method `storeDatasources`
246
+
247
+ With this method you can get all the datasources or one in particular. The datasources will be stored as `json` files in the `_data` folder or in the one specified through the `datasources_path` parameter of the `StoryblokTo11tyData` instance. Each datasource will be stored in a file with its name and in case you are requesting all of the datasources the name of the file will be `datasources.json`.
248
+
249
+ **Parameters**
250
+ - `[datasource_slug]` String, optional. The slug of the datasource you want to retrieve.
251
+
252
+ **Return**
253
+ Promise. Return `false` if something went wrong in the process, otherwise `true`.
254
+
255
+ **Examples**
256
+
257
+ **TypeScript:**
258
+ ```typescript
259
+ import { StoryblokTo11tyData } from 'storyblok-11ty';
260
+
261
+ const sb = new StoryblokTo11tyData({
262
+ token: 'your-space-token',
263
+ datasources_path: '_data/datasources'
264
+ });
265
+
266
+ // Store all datasources in datasources.json
267
+ await sb.storeDatasources();
268
+
269
+ // Store specific datasource (categories.json)
270
+ await sb.storeDatasources('categories');
271
+ ```
272
+
273
+ **JavaScript:**
274
+ ```javascript
275
+ const { StoryblokTo11tyData } = require('storyblok-11ty');
276
+ const sb = new StoryblokTo11tyData({token: 'your-space-token'});
277
+
278
+ // Store all datasources
279
+ await sb.storeDatasources();
280
+
281
+ // Store specific datasource
282
+ await sb.storeDatasources('categories');
283
+ ```
284
+
285
+ ## Eleventy Plugin
286
+
287
+ ### Class `StoryblokTo11tyPlugin`
288
+
289
+ **Parameters**
290
+
291
+ - `config` Object
292
+ - `blocks_folder` String, The folder of the blocks layouts. It should include the *includes* folder path just if you are using Nunjucks.
293
+
294
+ **TypeScript:**
295
+ ```typescript
296
+ import { StoryblokTo11tyPlugin } from 'storyblok-11ty';
297
+
298
+ export default function(eleventyConfig) {
299
+ const sbPlugin = new StoryblokTo11tyPlugin({blocks_folder: 'components/'});
300
+ eleventyConfig.addPlugin(sbPlugin);
301
+ }
302
+ ```
303
+
304
+ **JavaScript:**
305
+ ```javascript
306
+ const { StoryblokTo11tyPlugin } = require('storyblok-11ty');
307
+
308
+ module.exports = function(eleventyConfig) {
309
+ const sbPlugin = new StoryblokTo11tyPlugin({blocks_folder: 'components/'});
310
+ eleventyConfig.addPlugin(sbPlugin);
311
+ }
312
+ ```
313
+
314
+ ### Custom tag for blocks fields
315
+
316
+ If you have a field of type `block` and you have several blocks inside it, you might want to output all of them using a different layout file for each block.
317
+ In order to achieve this you can use a custom tag for Liquid and Nunjucks layouts.
318
+
319
+ #### sb_blocks for Liquid
320
+
321
+ The custom tag `sb_blocks` can be used like this `{% sb_blocks name_of_blocks_field %}` and it will loop through all the blocks inside the field. For each block it'll include a template with the same name as the slugified component name. If your block is called `Home Banner` the tag will look for the template `home-banner.liquid` inside the `_includes/block/` folder or inside `includes/your_custom_folder/`. You can specify `your_custom_folder` passing the parameter `blocks_folder` to the StoryblokTo11tyPlugin instance like in the example below. You don't need to add your *includes* folder path into the `blocks_folder` parameter because 11ty will take care of that for you.
322
+
323
+ The block fields will be passed to the layout under the object `block`. If your block has a field called `heading` you can retrieve its value referencing to it as `block.heading`.
324
+
325
+ **TypeScript:**
326
+ ```typescript
327
+ import { StoryblokTo11tyPlugin } from "storyblok-11ty";
328
+
329
+ export default function(eleventyConfig) {
330
+ const sbPlugin = new StoryblokTo11tyPlugin({blocks_folder: 'components/'});
331
+ eleventyConfig.addPlugin(sbPlugin);
332
+ }
333
+ ```
334
+
335
+ **JavaScript:**
336
+ ```javascript
337
+ const { StoryblokTo11tyPlugin } = require("storyblok-11ty");
338
+
339
+ module.exports = function(eleventyConfig) {
340
+ const sbPlugin = new StoryblokTo11tyPlugin({blocks_folder: 'components/'});
341
+ eleventyConfig.addPlugin(sbPlugin);
342
+ }
343
+ ```
344
+
345
+ #### sb_blocks for Nunjucks
346
+
347
+ The custom tag `sb_blocks` can be used like this `{% sb_blocks name_of_blocks_field %}` and it will loop through all the blocks inside the field. For each block it'll include a template with the same name as the slugified component name. If your block is called `Home Banner` the tag will look for the template `home-banner.njk` inside the `_includes/block/` folder or inside `includes/your_custom_folder/`. You must specify `your_custom_folder` passing the parameter `blocks_folder` to the StoryblokTo11tyPlugin instance like in the example below. You must add your *includes* folder path into the `blocks_folder` parameter to make the tag work properly, unfortunately it's not the same as for Liquid.
348
+
349
+ The block fields will be passed to the layout under the object `block`. If your block has a field called `heading` you can retrieve its value referencing to it as `block.heading`.
350
+
351
+ **TypeScript:**
352
+ ```typescript
353
+ import { StoryblokTo11tyPlugin } from "storyblok-11ty";
354
+
355
+ export default function(eleventyConfig) {
356
+ const sbPlugin = new StoryblokTo11tyPlugin({blocks_folder: '_includes/components/'});
357
+ eleventyConfig.addPlugin(sbPlugin);
358
+ }
359
+ ```
360
+
361
+ **JavaScript:**
362
+ ```javascript
363
+ const { StoryblokTo11tyPlugin } = require("storyblok-11ty");
364
+
365
+ module.exports = function(eleventyConfig) {
366
+ const sbPlugin = new StoryblokTo11tyPlugin({blocks_folder: '_includes/components/'});
367
+ eleventyConfig.addPlugin(sbPlugin);
368
+ }
369
+ ```
370
+
371
+ ### Custom tag for richtext fields
372
+
373
+ The custom tag `sb_richtext` can be used like this `{% sb_richtext name_of_richtext_field %}`. The content of the field will be rendered. The render method uses the [@storyblok/richtext](https://github.com/storyblok/richtext) package.
374
+
375
+ **Note**: This tag works the same for both Liquid and Nunjucks templates.
376
+
377
+ ## Available Types
378
+
379
+ The package exports the following TypeScript types:
380
+
381
+ ```typescript
382
+ import type {
383
+ StoryblokTo11tyConfig, // Configuration object for StoryblokTo11tyData
384
+ Story, // Story object from Storyblok
385
+ TransformedStory, // Transformed story with 11ty specific fields
386
+ DatasourceEntry, // Datasource entry object
387
+ GetStoriesParams, // Parameters for getStories method
388
+ ApiResponse // API response structure
389
+ } from 'storyblok-11ty';
390
+ ```
391
+
392
+ ## Development
393
+
394
+ ```bash
395
+ # Install dependencies
396
+ npm install
397
+
398
+ # Build the package
399
+ npm run build
400
+
401
+ # Run tests
402
+ npm test
403
+
404
+ # Run tests with coverage
405
+ npm run test:coverage
406
+
407
+ # Type check
408
+ npm run type-check
409
+
410
+ # Lint code
411
+ npm run lint
412
+
413
+ # Format code
414
+ npm run format
415
+ ```
416
+
417
+ ## License
418
+
419
+ MIT