@dosgato/templating 0.0.40 → 0.0.43

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.
@@ -188,11 +188,6 @@ export declare type AnyMigration = ComponentMigration | PageMigration | DataMigr
188
188
  export declare type LinkGatheringFn = (data: any) => LinkDefinition[];
189
189
  export declare type FulltextGatheringFn = (data: any) => string[];
190
190
  export declare type GraphQLQueryFn = <T>(query: string, variables?: any) => Promise<T>;
191
- /**
192
- * This function is used by API template definitions to help them identify links inside large blocks
193
- * of text and return them for indexing.
194
- */
195
- export declare function extractLinksFromText(text: string | undefined): LinkDefinition[];
196
191
  /**
197
192
  * This function is used by API template definitions to help them identify all the searchable
198
193
  * words in a large block of text and return them for indexing.
@@ -5,16 +5,6 @@ export var ValidationMessageType;
5
5
  ValidationMessageType["WARNING"] = "warning";
6
6
  ValidationMessageType["SUCCESS"] = "success";
7
7
  })(ValidationMessageType || (ValidationMessageType = {}));
8
- /**
9
- * This function is used by API template definitions to help them identify links inside large blocks
10
- * of text and return them for indexing.
11
- */
12
- export function extractLinksFromText(text) {
13
- if (!text)
14
- return [];
15
- const matches = text.matchAll(/{.*"type"\s?:\s+"\w+".*?}/gi);
16
- return Array.from(matches).map(m => JSON.parse(m[0]));
17
- }
18
8
  /**
19
9
  * This function is used by API template definitions to help them identify all the searchable
20
10
  * words in a large block of text and return them for indexing.
@@ -12,39 +12,6 @@ export declare abstract class Component<DataType extends ComponentData = any, Fe
12
12
  * Provide this when you create a template to identify what you are defining.
13
13
  */
14
14
  static templateKey: string;
15
- /**
16
- * These functions will be provided by the rendering server
17
- */
18
- static editBar: (path: string, opts: EditBarOpts) => string;
19
- static newBar: (path: string, opts: EditBarOpts) => string;
20
- areas: Map<string, Component<any, any, any>[]>;
21
- data: Omit<DataType, 'areas'>;
22
- fetched: FetchedType;
23
- renderCtx: RenderContextType;
24
- path: string;
25
- parent?: Component;
26
- page?: Page;
27
- hadError: boolean;
28
- autoLabel: () => string;
29
- autoNewLabel: () => string;
30
- /**
31
- * When hydrating an inherited component, the renderer will set this to the id of the page it
32
- * came from. You may use this information in any of the phases to alter your behavior if needed.
33
- *
34
- * For instance, you may decide that your fetch function needs some extra information from the
35
- * originating page instead of the page you're being inherited into (your `this.page` will
36
- * be the page currently being rendered, NOT the page the inheritable component came from).
37
- *
38
- * This property is also used to alter the edit bar. Inherited components may never be edited
39
- * except on their original page, so the edit bar will render with a link to the original page.
40
- */
41
- inheritedFrom?: string;
42
- /**
43
- * This property will be set during page render and you may refer to it at any time to
44
- * determine whether you are doing your work in edit mode or regular rendering mode.
45
- * The editBar and newBar methods will automatically use it to blank out the editing UI.
46
- */
47
- editMode: boolean;
48
15
  /**
49
16
  * The rendering server will provide an instance of the APIClient interface so that
50
17
  * you can run any API GraphQL query you like in your `fetch` function. There are also
@@ -56,32 +23,23 @@ export declare abstract class Component<DataType extends ComponentData = any, Fe
56
23
  */
57
24
  api: APIClient;
58
25
  /**
59
- * Retrieve the data for the root page of the page this component is on. Useful for
60
- * implementing inheritance schemes.
61
- *
62
- * This function will be provided by the rendering service.
63
- *
64
- * Do NOT mutate the data returned by this function, as it may be cached and given to
65
- * other Component instances.
26
+ * This property will be set during page render and you may refer to it at any time to
27
+ * determine whether you are doing your work in edit mode or regular rendering mode.
28
+ * The editBar and newBar methods will automatically use it to blank out the editing UI.
66
29
  */
67
- getRootPageData: () => Promise<PageData>;
30
+ editMode: boolean;
68
31
  /**
69
- * Retrieve the data for all ancestor pages of the page this component is on. Useful
70
- * for implementing inheritance schemes.
32
+ * When hydrating an inherited component, the renderer will set this to the id of the page it
33
+ * came from. You may use this information in any of the phases to alter your behavior if needed.
71
34
  *
72
- * This function will be provided by the rendering service.
35
+ * For instance, you may decide that your fetch function needs some extra information from the
36
+ * originating page instead of the page you're being inherited into (your `this.page` will
37
+ * be the page currently being rendered, NOT the page the inheritable component came from).
73
38
  *
74
- * Do NOT mutate the data returned by this function, as it may be cached and given to
75
- * other Component instances.
76
- */
77
- getAncestorPageData: () => Promise<PageData[]>;
78
- /**
79
- * Some components may be inheritable to subpages within the same site. For instance, a site's
80
- * social media links may appear on every page's footer. To accomplish this in your template,
81
- * you need to fetch the data in your fetch phase and then call this function within your fetch
82
- * to let the renderer know it needs to hydrate them and include them in the render.
39
+ * This property is also used to alter the edit bar. Inherited components may never be edited
40
+ * except on their original page, so the edit bar will render with a link to the original page.
83
41
  */
84
- registerInherited: (area: string, components: ComponentData[], top?: true) => void;
42
+ inheritedFrom?: string;
85
43
  /**
86
44
  * The first phase of rendering a component is the fetch phase. Each component may
87
45
  * provide a fetch method that looks up data it needs from external sources. This step
@@ -91,12 +49,27 @@ export declare abstract class Component<DataType extends ComponentData = any, Fe
91
49
  * Place any needed data into the return object, and it will be available to you as `this.fetched`
92
50
  * during the rendering phase.
93
51
  *
94
- * Note that this.page will be available, and getRootPageData and getAncestorPageData are
95
- * available in case there is a need for inheritance. If you need to inherit entire components,
96
- * you may add them to your this.areas map, e.g.
97
- * `this.areas.get('myarea').push(new Component(inheritedData, this.path + '/myarea/inherit1', this))`
52
+ * Note that `this.page` will be available, and `this.api` has dataloaded methods for retrieving
53
+ * data from the API if, for instance, you need to inherit information from a parent or root
54
+ * page. If you need to inherit and render entire components from ancestor pages,
55
+ * you must register them. See the comment for `this.registerInherited`
56
+ *
57
+ * Try to minimize the number of round trips you make here, make use of Promise.all and such;
58
+ * remember that the api functions are mostly dataloaded so calling them simultaneously is
59
+ * advantageous where possible.
98
60
  */
99
61
  fetch(): Promise<FetchedType>;
62
+ /**
63
+ * Some components may be inheritable to subpages within the same site. For instance, a site's
64
+ * social media links may appear on every page's footer. To accomplish this in your template,
65
+ * you need to fetch ancestor page data in your fetch phase, identify the component data you want
66
+ * to inherit, and then call this function within your fetch to let the renderer know it needs to
67
+ * process those components (hydrate them, call their fetch functions, and include them in the render).
68
+ *
69
+ * The inherited components will be added to the appropriate area's array in the renderedAreas
70
+ * parameter of your render function.
71
+ */
72
+ registerInherited: (area: string, components: ComponentData[], top?: true) => void;
100
73
  /**
101
74
  * The second phase of rendering a component is the context phase. This step is TOP-DOWN and
102
75
  * NON-MUTATING. Each component will receive the parent component's context and then pass a
@@ -119,7 +92,7 @@ export declare abstract class Component<DataType extends ComponentData = any, Fe
119
92
  * render will be passed to parent components so that the HTML can be included during the
120
93
  * render of the parent component.
121
94
  */
122
- abstract render(renderedAreas: Map<string, RenderedComponent[]>): string;
95
+ abstract render(): string;
123
96
  /**
124
97
  * Sometimes pages are requested with an alternate extension like .rss or .ics. In these
125
98
  * situations, each component should consider whether it should output anything. For
@@ -132,16 +105,8 @@ export declare abstract class Component<DataType extends ComponentData = any, Fe
132
105
  * This function will be run after the fetch phase. The context and html rendering phases
133
106
  * will be skipped.
134
107
  */
135
- renderVariation(extension: string, renderedAreas: Map<string, string>): string;
136
- constructor(data: DataType, path: string, parent: Component | undefined, editMode: boolean);
137
- /**
138
- * For logging errors during rendering without crashing the render. If your fetch, setContext,
139
- * render, or renderVariation functions throw, the error will be logged but the page render will
140
- * continue. You generally do not need to use this function, just throw when appropriate.
141
- */
142
- logError(e: Error): void;
143
- protected passError(e: Error, path: string): void;
144
- renderComponents(components?: RenderedComponent[], opts?: {
108
+ renderVariation(extension: string): string;
109
+ renderComponents(components?: RenderedComponent[] | string, opts?: {
145
110
  hideInheritBars?: boolean;
146
111
  }): string;
147
112
  /**
@@ -152,6 +117,8 @@ export declare abstract class Component<DataType extends ComponentData = any, Fe
152
117
  *
153
118
  * This is evaluated after the fetch and context phases but before the rendering phase. If you
154
119
  * need any async data to make this determination, be sure to fetch it during the fetch phase.
120
+ *
121
+ * You should check `this.editMode` if you need to load CSS that alters edit bars.
155
122
  */
156
123
  cssBlocks(): string[];
157
124
  /**
@@ -195,6 +162,32 @@ export declare abstract class Component<DataType extends ComponentData = any, Fe
195
162
  * Generally should not be overridden - override newLabel and newClass instead
196
163
  */
197
164
  newBar(areaName: string, opts?: EditBarOpts): string;
165
+ /**
166
+ * These functions will be provided by the rendering server to assist in the
167
+ * rendering process.
168
+ */
169
+ static editBar: (path: string, opts: EditBarOpts) => string;
170
+ static newBar: (path: string, opts: EditBarOpts) => string;
171
+ static repairHTML: (html: string) => string;
172
+ constructor(data: DataType, path: string, parent: Component | undefined, editMode: boolean);
173
+ areas: Map<string, Component<any, any, any>[]>;
174
+ data: Omit<DataType, 'areas'>;
175
+ fetched: FetchedType;
176
+ renderCtx: RenderContextType;
177
+ path: string;
178
+ parent?: Component;
179
+ page?: Page;
180
+ renderedAreas: Map<string, RenderedComponent[]>;
181
+ hadError: boolean;
182
+ autoLabel: string;
183
+ autoNewLabel: string;
184
+ /**
185
+ * For logging errors during rendering without crashing the render. If your fetch, setContext,
186
+ * render, or renderVariation functions throw, the error will be logged but the page render will
187
+ * continue. You generally do not need to use this function, just throw when appropriate.
188
+ */
189
+ logError(e: Error): void;
190
+ protected passError(e: Error, path: string): void;
198
191
  }
199
192
  export interface PageRecord<DataType extends PageData = PageData> {
200
193
  id: string;
@@ -231,17 +224,25 @@ export interface EditBarOpts {
231
224
  editMode?: boolean;
232
225
  inheritedFrom?: string;
233
226
  }
234
- export interface RenderedComponent {
235
- inherited: boolean;
236
- html: string;
237
- editbar: string;
227
+ export interface RenderedComponent<C extends Component = Component> {
228
+ component: C;
229
+ output: string;
238
230
  }
239
231
  export declare abstract class Page<DataType extends PageData = any, FetchedType = any, RenderContextType extends ContextBase = any> extends Component<DataType, FetchedType, RenderContextType> {
240
232
  pagePath: string;
241
233
  /**
242
- * we will fill this before rendering, stuff that dosgato knows needs to be added to
243
- * the <head> element
244
- * the page's render function must include it
234
+ * This will be filled by the rendering server. The template properties are described
235
+ * over in apitemplate.ts in the comment for APIPageTemplate.templateProperties.
236
+ *
237
+ * The properties will appear in the GraphQL API and the rendering server will automatically
238
+ * download them and provide them here so all you need to do as a template developer is
239
+ * reference the values in your fetch/setContext/render functions.
240
+ */
241
+ templateProperties: any;
242
+ /**
243
+ * This is a bunch of javascript and CSS and meta tags managed by the DosGato engine. It will
244
+ * be filled by the rendering server and your render function for your page template
245
+ * should place include it in the <head> element
245
246
  */
246
247
  headContent: string;
247
248
  protected passError(e: Error, path: string): void;
package/dist/component.js CHANGED
@@ -12,9 +12,9 @@ export class Component extends ResourceProvider {
12
12
  // a Component will also construct/hydrate all its child components
13
13
  constructor(data, path, parent, editMode) {
14
14
  super();
15
- // properties for use during hydration, you do not have to provide these when
15
+ // Properties provided during the rendering process. You do not have to provide these when
16
16
  // building a template, but you can use them in the functions you do provide
17
- this.areas = new Map();
17
+ this.areas = new Map(); // a Map of area names and the array of hydrated components in each
18
18
  this.editMode = editMode;
19
19
  this.parent = parent;
20
20
  const { areas, ...ownData } = data;
@@ -37,10 +37,14 @@ export class Component extends ResourceProvider {
37
37
  * Place any needed data into the return object, and it will be available to you as `this.fetched`
38
38
  * during the rendering phase.
39
39
  *
40
- * Note that this.page will be available, and getRootPageData and getAncestorPageData are
41
- * available in case there is a need for inheritance. If you need to inherit entire components,
42
- * you may add them to your this.areas map, e.g.
43
- * `this.areas.get('myarea').push(new Component(inheritedData, this.path + '/myarea/inherit1', this))`
40
+ * Note that `this.page` will be available, and `this.api` has dataloaded methods for retrieving
41
+ * data from the API if, for instance, you need to inherit information from a parent or root
42
+ * page. If you need to inherit and render entire components from ancestor pages,
43
+ * you must register them. See the comment for `this.registerInherited`
44
+ *
45
+ * Try to minimize the number of round trips you make here, make use of Promise.all and such;
46
+ * remember that the api functions are mostly dataloaded so calling them simultaneously is
47
+ * advantageous where possible.
44
48
  */
45
49
  async fetch() {
46
50
  return undefined;
@@ -75,26 +79,15 @@ export class Component extends ResourceProvider {
75
79
  * This function will be run after the fetch phase. The context and html rendering phases
76
80
  * will be skipped.
77
81
  */
78
- renderVariation(extension, renderedAreas) {
79
- return Array.from(renderedAreas.values()).join('');
80
- }
81
- /**
82
- * For logging errors during rendering without crashing the render. If your fetch, setContext,
83
- * render, or renderVariation functions throw, the error will be logged but the page render will
84
- * continue. You generally do not need to use this function, just throw when appropriate.
85
- */
86
- logError(e) {
87
- this.hadError = true;
88
- this.passError(e, this.path);
89
- }
90
- // helper function for recursively passing the error up until it reaches the page
91
- passError(e, path) {
92
- this.parent?.passError(e, path);
82
+ renderVariation(extension) {
83
+ return Array.from(this.renderedAreas.values()).flatMap(ras => ras.map(ra => ra.output)).join('');
93
84
  }
94
85
  // helper function to help you print an area, but you can also override this if you
95
86
  // need to do something advanced like wrap each component in a div
96
87
  renderComponents(components = [], opts) {
97
- return components.flatMap(c => c.inherited && opts?.hideInheritBars ? [c.html] : [c.editbar, c.html]).join('');
88
+ if (!Array.isArray(components))
89
+ components = this.renderedAreas.get(components) ?? [];
90
+ return components.flatMap(c => c.component.inheritedFrom && opts?.hideInheritBars ? [c.output] : [c.component.editBar(), c.output]).join('');
98
91
  }
99
92
  /**
100
93
  * During rendering, each component should determine the CSS blocks that it needs. This may
@@ -104,6 +97,8 @@ export class Component extends ResourceProvider {
104
97
  *
105
98
  * This is evaluated after the fetch and context phases but before the rendering phase. If you
106
99
  * need any async data to make this determination, be sure to fetch it during the fetch phase.
100
+ *
101
+ * You should check `this.editMode` if you need to load CSS that alters edit bars.
107
102
  */
108
103
  cssBlocks() {
109
104
  return Array.from(this.constructor.cssBlocks.keys());
@@ -120,16 +115,12 @@ export class Component extends ResourceProvider {
120
115
  *
121
116
  * For instance, you could return this.data.title
122
117
  */
123
- editLabel() {
124
- return undefined;
125
- }
118
+ editLabel() { return undefined; }
126
119
  /**
127
120
  * Components may override this function to give their edit bars a custom
128
121
  * CSS class
129
122
  */
130
- editClass() {
131
- return undefined;
132
- }
123
+ editClass() { return undefined; }
133
124
  /**
134
125
  * Components may override this function to give their new bars a custom
135
126
  * label
@@ -137,23 +128,19 @@ export class Component extends ResourceProvider {
137
128
  * For instance, an area that only accepts 'layout' components could
138
129
  * return "Add Layout"
139
130
  */
140
- newLabel(areaName) {
141
- return undefined;
142
- }
131
+ newLabel(areaName) { return undefined; }
143
132
  /**
144
133
  * Components may override this function to give their new bars a custom
145
134
  * CSS class
146
135
  */
147
- newClass(areaName) {
148
- return undefined;
149
- }
136
+ newClass(areaName) { return undefined; }
150
137
  /**
151
138
  * Components may override this function to provide a custom edit bar
152
139
  *
153
140
  * Generally should not be overridden - override editLabel and editClass instead
154
141
  */
155
142
  editBar(opts = {}) {
156
- opts.label ?? (opts.label = this.editLabel() ?? this.autoLabel());
143
+ opts.label ?? (opts.label = this.editLabel() ?? this.autoLabel);
157
144
  opts.extraClass ?? (opts.extraClass = this.editClass());
158
145
  opts.editMode ?? (opts.editMode = this.editMode);
159
146
  opts.inheritedFrom ?? (opts.inheritedFrom = this.inheritedFrom);
@@ -165,12 +152,25 @@ export class Component extends ResourceProvider {
165
152
  * Generally should not be overridden - override newLabel and newClass instead
166
153
  */
167
154
  newBar(areaName, opts = {}) {
168
- opts.label ?? (opts.label = this.newLabel(areaName) ?? this.autoNewLabel());
155
+ opts.label ?? (opts.label = this.newLabel(areaName) ?? this.autoNewLabel);
169
156
  opts.extraClass ?? (opts.extraClass = this.newClass(areaName));
170
157
  opts.editMode ?? (opts.editMode = this.editMode);
171
158
  opts.inheritedFrom ?? (opts.inheritedFrom = this.inheritedFrom);
172
159
  return Component.newBar([this.path, 'areas', areaName].filter(isNotBlank).join('.'), opts);
173
160
  }
161
+ /**
162
+ * For logging errors during rendering without crashing the render. If your fetch, setContext,
163
+ * render, or renderVariation functions throw, the error will be logged but the page render will
164
+ * continue. You generally do not need to use this function, just throw when appropriate.
165
+ */
166
+ logError(e) {
167
+ this.hadError = true;
168
+ this.passError(e, this.path);
169
+ }
170
+ // helper function for recursively passing the error up until it reaches the page
171
+ passError(e, path) {
172
+ this.parent?.passError(e, path);
173
+ }
174
174
  }
175
175
  export class Page extends Component {
176
176
  constructor(page, editMode) {
package/dist/links.d.ts CHANGED
@@ -63,3 +63,13 @@ export interface DataFolderLink {
63
63
  path: string;
64
64
  }
65
65
  export declare type LinkDefinition = AssetLink | AssetFolderLink | PageLink | WebLink | DataLink | DataFolderLink;
66
+ /**
67
+ * This function is used by template definitions to help them identify links inside large blocks
68
+ * of text and return them for indexing, and by render definitions to help replace them with the actual URLs
69
+ */
70
+ export declare function extractLinksFromText(text: string | undefined): LinkDefinition[];
71
+ /**
72
+ * This function is used by render definitions to replace links in large blocks with the actual
73
+ * URLs they point to at render time.
74
+ */
75
+ export declare function replaceLinksInText(text: string, resolved: Map<string, string>): string;
package/dist/links.js CHANGED
@@ -1 +1,19 @@
1
- export {};
1
+ const LinkRegex = /{.*"type"\s?:\s+"\w+".*?}/g;
2
+ /**
3
+ * This function is used by template definitions to help them identify links inside large blocks
4
+ * of text and return them for indexing, and by render definitions to help replace them with the actual URLs
5
+ */
6
+ export function extractLinksFromText(text) {
7
+ if (!text)
8
+ return [];
9
+ const matches = text.matchAll(LinkRegex);
10
+ return Array.from(matches).map(m => JSON.parse(m[0]));
11
+ }
12
+ /**
13
+ * This function is used by render definitions to replace links in large blocks with the actual
14
+ * URLs they point to at render time.
15
+ */
16
+ export function replaceLinksInText(text, resolved) {
17
+ // TODO: figure out a broken link to use instead of '#', so it can be detected later
18
+ return text.replace(LinkRegex, m => resolved.get(m) ?? '#');
19
+ }
package/dist/render.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ContextBase, DataData, PageData } from './component.js';
1
+ import { ContextBase, DataData, PageData, PageRecord } from './component.js';
2
2
  import { AssetLink, DataFolderLink, DataLink, LinkDefinition } from './links.js';
3
3
  export declare function printHeader(ctx: ContextBase, content: string): string;
4
4
  export declare function advanceHeader(ctx: ContextBase, content?: string): {
@@ -42,7 +42,7 @@ export interface APIClient {
42
42
  */
43
43
  query: <T = any>(query: string, variables?: any) => Promise<T>;
44
44
  /**
45
- * This function will be provided by the rendering server and should be used inside your fetch,
45
+ * This function will be provided by the rendering server and should be used inside your fetch
46
46
  * method to convert a link, as input by a user, into a URL suitable for an href, or optionally
47
47
  * an absolute URL suitable for a backend http request or non-HTML document like an RSS feed.
48
48
  */
@@ -61,23 +61,40 @@ export interface APIClient {
61
61
  * The alt text it returns will be the default alternative text from the asset repository. Alt
62
62
  * text gathered from a template's dialog should generally take precedence (though the dialog may
63
63
  * preload the alt text field with the asset repository default).
64
+ *
65
+ * Will be dataloaded.
64
66
  */
65
67
  getImgAttributes: (link: string | AssetLink, absolute?: boolean) => Promise<PictureAttributes>;
66
- /** Get the data for a specific page. Will be dataloaded. */
68
+ /** Get the data for a specific page.
69
+ *
70
+ * Will be dataloaded.
71
+ */
67
72
  getPageData: ({ id, path }: {
68
73
  id?: string;
69
74
  path?: string;
70
75
  }) => Promise<PageData>;
76
+ /** Get all ancestor pages of a specific page. First array element will be the pagetree root page. */
77
+ getAncestors: ({ id, path }: {
78
+ id?: string;
79
+ path?: string;
80
+ }) => Promise<PageRecord[]>;
81
+ /** Get the pagetree root page from which the specified page descends. */
82
+ getRootPageData: ({ id, path }: {
83
+ id?: string;
84
+ path?: string;
85
+ }) => Promise<PageData>;
71
86
  /**
72
- * Get data items
87
+ * Get data entries by link or folder link
73
88
  *
74
89
  * Returns an array in case link is a DataFolderLink. If link is a DataLink, will return an
75
90
  * array with length <= 1.
76
91
  */
77
92
  getDataByLink: (link: string | DataLink | DataFolderLink) => Promise<DataData[]>;
78
93
  /**
79
- * Get data by full path including site. Use '/global' for global data. If path refers
80
- * to a specific data item, will return an array with length <= 1.
94
+ * Get data entries by full path including site
95
+ *
96
+ * Use '/global' for global data. If path refers to a specific data item, will return
97
+ * an array with length <= 1.
81
98
  */
82
99
  getDataByPath: (templateKey: string, path: string) => Promise<DataData[]>;
83
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosgato/templating",
3
- "version": "0.0.40",
3
+ "version": "0.0.43",
4
4
  "description": "A library to support building templates for dosgato CMS.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,7 +15,7 @@
15
15
  "dependencies": {
16
16
  "@iconify/svelte": "^2.2.1",
17
17
  "svelte": "^3.48.0",
18
- "txstate-utils": "^1.7.1"
18
+ "txstate-utils": "^1.7.4"
19
19
  },
20
20
  "devDependencies": {
21
21
  "eslint-config-standard-with-typescript": "^22.0.0",