@allejo/decap-extras 0.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.
- package/README.md +270 -0
- package/dist/index.d.mts +345 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +128 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# @allejo/decap-extras
|
|
2
|
+
|
|
3
|
+
A TypeScript utility library for [Decap CMS](https://decapcms.org/) that does two things:
|
|
4
|
+
|
|
5
|
+
1. **Widget factory functions** — type-safe helpers for building Decap CMS field configuration objects instead of writing raw object literals by hand.
|
|
6
|
+
2. **Type inference utilities** — TypeScript utility types that derive content types directly from your CMS config, so you never have to maintain separate type definitions for your CMS content.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @allejo/decap-extras
|
|
12
|
+
# or
|
|
13
|
+
pnpm add @allejo/decap-extras
|
|
14
|
+
# or
|
|
15
|
+
yarn add @allejo/decap-extras
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This package requires `decap-cms-core` as a peer dependency:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install decap-cms-core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Widget factories
|
|
27
|
+
|
|
28
|
+
All factory functions follow the same signature: `widget(label, name, options?)`.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
BARE_MARKDOWN,
|
|
33
|
+
boolWidget,
|
|
34
|
+
imageWidget,
|
|
35
|
+
INLINE_MARKDOWN,
|
|
36
|
+
listWidget,
|
|
37
|
+
markdownWidget,
|
|
38
|
+
numberWidget,
|
|
39
|
+
objectWidget,
|
|
40
|
+
optional,
|
|
41
|
+
selectWidget,
|
|
42
|
+
stringWidget,
|
|
43
|
+
textWidget,
|
|
44
|
+
} from '@allejo/decap-extras';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Scalar widgets
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const titleField = stringWidget('Title', 'title');
|
|
51
|
+
const bodyField = textWidget('Body', 'body');
|
|
52
|
+
const countField = numberWidget('Count', 'count');
|
|
53
|
+
const activeField = boolWidget('Active', 'active');
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Select widget
|
|
57
|
+
|
|
58
|
+
Pass the choices array `as const` to get a narrowed literal union type; omit `as const` to get `string`.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const themeField = selectWidget('Theme', 'theme', ['light', 'dark'] as const);
|
|
62
|
+
// TypeScript type: "light" | "dark"
|
|
63
|
+
|
|
64
|
+
const looseField = selectWidget('Size', 'size', ['sm', 'md', 'lg']);
|
|
65
|
+
// TypeScript type: string
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Object widget
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const metaField = objectWidget('Meta', 'meta', [
|
|
72
|
+
stringWidget('Title', 'title'),
|
|
73
|
+
stringWidget('Description', 'description'),
|
|
74
|
+
]);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### List widget
|
|
78
|
+
|
|
79
|
+
Pass a single field to get `Array<T>`, or an array of fields to get `Array<{ ... }>`.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// Single-field list → Array<string>
|
|
83
|
+
const tagsField = listWidget('Tags', 'tags', stringWidget('Tag', 'tag'));
|
|
84
|
+
|
|
85
|
+
// Multi-field list → Array<{ label: string; value: string }>
|
|
86
|
+
const tagObjectsField = listWidget('Tag Objects', 'tagObjects', [
|
|
87
|
+
stringWidget('Label', 'label'),
|
|
88
|
+
stringWidget('Value', 'value'),
|
|
89
|
+
]);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Image widget
|
|
93
|
+
|
|
94
|
+
`imageWidget` is pre-configured with [Cloudinary](https://cloudinary.com/) as the media library, with default transformations (`fetch_format: auto`, `quality: auto`, `width: 300`, `crop: scale`). You can override these via the `options` parameter.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const heroField = imageWidget('Hero Image', 'hero');
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Markdown widget
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// Full markdown editor
|
|
104
|
+
const contentField = markdownWidget('Content', 'content');
|
|
105
|
+
|
|
106
|
+
// Restricted to bold, italic, lists, links, and quotes — no images or code blocks
|
|
107
|
+
const descriptionField = markdownWidget(
|
|
108
|
+
'Description',
|
|
109
|
+
'description',
|
|
110
|
+
BARE_MARKDOWN,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Inline elements only: bold, italic, and links
|
|
114
|
+
const captionField = markdownWidget('Caption', 'caption', INLINE_MARKDOWN);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### Optional fields
|
|
118
|
+
|
|
119
|
+
Wrap any widget with `optional()` to mark it as not required in both the Decap CMS UI and the derived TypeScript type.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const subtitleField = optional(stringWidget('Subtitle', 'subtitle'));
|
|
123
|
+
// TypeScript type: string | undefined
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Common options
|
|
127
|
+
|
|
128
|
+
Every factory accepts an optional last argument for [common widget options](https://decapcms.org/docs/widgets/#common-widget-options):
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const slugField = stringWidget('Slug', 'slug', {
|
|
132
|
+
required: true,
|
|
133
|
+
hint: 'URL-friendly identifier, e.g. my-page-title',
|
|
134
|
+
pattern: ['^[a-z0-9-]+$', 'Lowercase letters, numbers, and hyphens only'],
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### Type inference
|
|
141
|
+
|
|
142
|
+
Define your CMS config using the widget factories and `as const`, then let `PropsByCollectionAndFile` derive a fully typed content object from it automatically.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// cms/config.ts
|
|
146
|
+
import {
|
|
147
|
+
BARE_MARKDOWN,
|
|
148
|
+
boolWidget,
|
|
149
|
+
imageWidget,
|
|
150
|
+
markdownWidget,
|
|
151
|
+
objectWidget,
|
|
152
|
+
optional,
|
|
153
|
+
selectWidget,
|
|
154
|
+
stringWidget,
|
|
155
|
+
} from '@allejo/decap-extras';
|
|
156
|
+
|
|
157
|
+
export const config = {
|
|
158
|
+
collections: [
|
|
159
|
+
{
|
|
160
|
+
label: 'Pages',
|
|
161
|
+
name: 'pages' as const,
|
|
162
|
+
files: [
|
|
163
|
+
{
|
|
164
|
+
label: 'Home',
|
|
165
|
+
name: 'home' as const,
|
|
166
|
+
file: 'content/home.yml',
|
|
167
|
+
fields: [
|
|
168
|
+
stringWidget('Title', 'title'),
|
|
169
|
+
optional(stringWidget('Subtitle', 'subtitle')),
|
|
170
|
+
imageWidget('Hero Image', 'hero'),
|
|
171
|
+
markdownWidget('Body', 'body', BARE_MARKDOWN),
|
|
172
|
+
selectWidget('Theme', 'theme', ['light', 'dark'] as const),
|
|
173
|
+
objectWidget('SEO', 'seo', [
|
|
174
|
+
stringWidget('Meta Title', 'metaTitle'),
|
|
175
|
+
optional(stringWidget('Meta Description', 'metaDescription')),
|
|
176
|
+
]),
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
} as const;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// components/HomePage.ts
|
|
187
|
+
import type { PropsByCollectionAndFile } from '@allejo/decap-extras';
|
|
188
|
+
|
|
189
|
+
import type { config } from '@/cms/config';
|
|
190
|
+
|
|
191
|
+
type HomePageProps = PropsByCollectionAndFile<typeof config, 'pages', 'home'>;
|
|
192
|
+
// Resolves to:
|
|
193
|
+
// {
|
|
194
|
+
// title: string;
|
|
195
|
+
// subtitle?: string;
|
|
196
|
+
// hero: string;
|
|
197
|
+
// body: string;
|
|
198
|
+
// theme: "light" | "dark";
|
|
199
|
+
// seo: {
|
|
200
|
+
// metaTitle: string;
|
|
201
|
+
// metaDescription?: string;
|
|
202
|
+
// };
|
|
203
|
+
// }
|
|
204
|
+
|
|
205
|
+
function render(page: HomePageProps) {
|
|
206
|
+
console.log(page.title); // string
|
|
207
|
+
console.log(page.subtitle); // string | undefined
|
|
208
|
+
console.log(page.theme); // "light" | "dark"
|
|
209
|
+
console.log(page.seo.metaTitle); // string
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Typing reusable widget functions
|
|
214
|
+
|
|
215
|
+
Use `WidgetTypeFromFactory` to derive a TypeScript type from a widget factory function without duplicating the definition.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import type { WidgetTypeFromFactory } from '@allejo/decap-extras';
|
|
219
|
+
import { objectWidget, optional, stringWidget } from '@allejo/decap-extras';
|
|
220
|
+
|
|
221
|
+
function seoWidget() {
|
|
222
|
+
return objectWidget('SEO', 'seo', [
|
|
223
|
+
stringWidget('Meta Title', 'metaTitle'),
|
|
224
|
+
optional(stringWidget('Meta Description', 'metaDescription')),
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
type SeoWidget = WidgetTypeFromFactory<typeof seoWidget>;
|
|
229
|
+
// { metaTitle: string; metaDescription?: string }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## API reference
|
|
235
|
+
|
|
236
|
+
### Widget functions
|
|
237
|
+
|
|
238
|
+
| Function | Decap widget | TypeScript type |
|
|
239
|
+
| -------------------------------------------- | -------------------------------------------------------------- | ------------------------- |
|
|
240
|
+
| `stringWidget(label, name, opts?)` | [`string`](https://decapcms.org/docs/widgets/#string) | `string` |
|
|
241
|
+
| `textWidget(label, name, opts?)` | [`text`](https://decapcms.org/docs/widgets/#text) | `string` |
|
|
242
|
+
| `markdownWidget(label, name, opts?)` | [`richtext`](https://decapcms.org/docs/widgets/#richtext-beta) | `string` |
|
|
243
|
+
| `imageWidget(label, name, opts?)` | [`image`](https://decapcms.org/docs/widgets/#image) | `string` |
|
|
244
|
+
| `numberWidget(label, name, opts?)` | [`number`](https://decapcms.org/docs/widgets/#number) | `number` |
|
|
245
|
+
| `boolWidget(label, name, opts?)` | [`boolean`](https://decapcms.org/docs/widgets/#boolean) | `boolean` |
|
|
246
|
+
| `selectWidget(label, name, choices, opts?)` | [`select`](https://decapcms.org/docs/widgets/#select) | literal union or `string` |
|
|
247
|
+
| `listWidget(label, name, field, opts?)` | [`list`](https://decapcms.org/docs/widgets/#list) | `Array<T>` |
|
|
248
|
+
| `listWidget(label, name, fields[], opts?)` | [`list`](https://decapcms.org/docs/widgets/#list) | `Array<{ ... }>` |
|
|
249
|
+
| `objectWidget(label, name, fields[], opts?)` | [`object`](https://decapcms.org/docs/widgets/#object) | `{ ... }` |
|
|
250
|
+
| `optional(widget)` | — | marks field optional |
|
|
251
|
+
|
|
252
|
+
### Markdown presets
|
|
253
|
+
|
|
254
|
+
| Constant | Description |
|
|
255
|
+
| ----------------- | ------------------------------------------------------------- |
|
|
256
|
+
| `BARE_MARKDOWN` | Bold, italic, lists, links, quotes. No images or code blocks. |
|
|
257
|
+
| `INLINE_MARKDOWN` | Bold, italic, and links only. No block-level elements. |
|
|
258
|
+
|
|
259
|
+
### Type utilities
|
|
260
|
+
|
|
261
|
+
| Type | Description |
|
|
262
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
263
|
+
| `PropsByCollectionAndFile<TConfig, C, F>` | Derives a typed content object from a CMS config for collection `C` and file `F`. |
|
|
264
|
+
| `WidgetTypeFromFactory<F>` | Returns the TypeScript content type produced by a widget factory function `F`. |
|
|
265
|
+
| `OptionalWidget<T>` | Brands a `CmsField` as optional (added by `optional()`). |
|
|
266
|
+
| `WidgetOpts<T>` | The options type for a given widget field type, with `widget` and `fields` stripped. |
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { CmsField, CmsFieldBoolean, CmsFieldFileOrImage, CmsFieldList, CmsFieldMarkdown, CmsFieldNumber, CmsFieldObject, CmsFieldSelect, CmsFieldStringOrText } from "decap-cms-core";
|
|
2
|
+
|
|
3
|
+
//#region src/api.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Get a widget's options while excluding keys we're handling (i.e., `widget`,
|
|
6
|
+
* `fields`) and respect [global common options](https://decapcms.org/docs/widgets/#String#common-widget-options).
|
|
7
|
+
*/
|
|
8
|
+
type WidgetOpts<T extends {
|
|
9
|
+
widget?: string;
|
|
10
|
+
fields?: unknown[];
|
|
11
|
+
}> = Omit<T, 'widget' | 'fields'> & {
|
|
12
|
+
required?: boolean;
|
|
13
|
+
hint?: string;
|
|
14
|
+
pattern?: [string, string];
|
|
15
|
+
};
|
|
16
|
+
type OptionalWidget<T extends CmsField> = T & {
|
|
17
|
+
required: false;
|
|
18
|
+
__optional: true;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Recursively expands a type alias into its fully resolved shape.
|
|
22
|
+
* TypeScript does not eagerly expand type aliases in IDE hover tooltips —
|
|
23
|
+
* intermediate type aliases like `FieldsToObject<...>` are shown as-is rather
|
|
24
|
+
* than their resolved structure. Wrapping a type with this helper forces the
|
|
25
|
+
* IDE to display the fully resolved object shape at every level of nesting.
|
|
26
|
+
* This has no effect on type safety or runtime behavior; it is purely cosmetic.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Without this helper, the IDE may show:
|
|
30
|
+
* // type SectionWidget = FieldsToObject<[...]>
|
|
31
|
+
* //
|
|
32
|
+
* // With this helper, the IDE shows:
|
|
33
|
+
* // type SectionWidget = {
|
|
34
|
+
* // images: Array<{ polaroid: { image: string; altText?: string; caption: string } }>;
|
|
35
|
+
* // text: string;
|
|
36
|
+
* // imagePosition: "left" | "right";
|
|
37
|
+
* // }
|
|
38
|
+
*/
|
|
39
|
+
type IDE_HACK_ExpandRecursiveType<T> = T extends infer O ? { [K in keyof O]: O[K] extends object ? IDE_HACK_ExpandRecursiveType<O[K]> : O[K] } : never;
|
|
40
|
+
/**
|
|
41
|
+
* Recursively finds the first item in a readonly tuple/array whose `Key`
|
|
42
|
+
* property matches `Value`.
|
|
43
|
+
*
|
|
44
|
+
* Resolves to `never` when no matching item exists.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* type Found = Lookup<
|
|
48
|
+
* [{ name: "home"; file: "home.yml" }, { name: "about"; file: "about.yml" }],
|
|
49
|
+
* "name",
|
|
50
|
+
* "about"
|
|
51
|
+
* >;
|
|
52
|
+
* // { name: "about"; file: "about.yml" }
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* type Missing = Lookup<[{ name: "home"; file: "home.yml" }], "name", "contact">;
|
|
56
|
+
* // never
|
|
57
|
+
*/
|
|
58
|
+
type Lookup<Array extends readonly unknown[], Key extends string, Value extends string> = Array extends readonly [infer First, ...infer Rest] ? First extends Record<Key, Value> ? First : Lookup<Rest, Key, Value> : never;
|
|
59
|
+
/**
|
|
60
|
+
* Get all the collection definitions as a data type from a given config.
|
|
61
|
+
*/
|
|
62
|
+
type CollectionTypes<TConfig extends {
|
|
63
|
+
collections: readonly unknown[];
|
|
64
|
+
}> = TConfig['collections'][number];
|
|
65
|
+
/**
|
|
66
|
+
* Get all the collection names from `CollectionTypes` as a union of string
|
|
67
|
+
* literal types to later use with `Lookup<...>`.
|
|
68
|
+
*/
|
|
69
|
+
type CollectionNames<TConfig extends {
|
|
70
|
+
collections: readonly unknown[];
|
|
71
|
+
}> = CollectionTypes<TConfig> extends {
|
|
72
|
+
name: infer N;
|
|
73
|
+
} ? N & string : never;
|
|
74
|
+
/**
|
|
75
|
+
* Get the specific collection structure for a given collection name. For
|
|
76
|
+
* example, `CollectionByName<Config, "pages">` would resolve to the collection
|
|
77
|
+
* definition object for the "Pages" collection in the config,
|
|
78
|
+
* which would look something like,
|
|
79
|
+
*
|
|
80
|
+
* ```
|
|
81
|
+
* {
|
|
82
|
+
* label: "Pages",
|
|
83
|
+
* name: "pages",
|
|
84
|
+
* files: ({
|
|
85
|
+
* label: "Home",
|
|
86
|
+
* name: "home",
|
|
87
|
+
* ...
|
|
88
|
+
* })[]
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
type CollectionByName<TConfig extends {
|
|
93
|
+
collections: readonly unknown[];
|
|
94
|
+
}, N extends CollectionNames<TConfig>> = Lookup<TConfig['collections'], 'name', N>;
|
|
95
|
+
/**
|
|
96
|
+
* Get the specific fields defined for a given page. For example,
|
|
97
|
+
* `FileByName<"site/pages/home.yml">` will return the fields defined for the
|
|
98
|
+
* "home.yml" file in the config, which would look something like,
|
|
99
|
+
*
|
|
100
|
+
* ```
|
|
101
|
+
* {
|
|
102
|
+
* label: "Welcome Section",
|
|
103
|
+
* name: "welcomeSection",
|
|
104
|
+
* widget: "object",
|
|
105
|
+
* fields: (...)[]
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
type FileByName<C extends {
|
|
110
|
+
files: readonly unknown[];
|
|
111
|
+
}, F extends (C['files'][number] extends {
|
|
112
|
+
name: infer N;
|
|
113
|
+
} ? N & string : never)> = Lookup<C['files'], 'name', F>;
|
|
114
|
+
/**
|
|
115
|
+
* The bare minimum that every file definition in the config must have to be
|
|
116
|
+
* able to look up its fields and translate them to TypeScript types.
|
|
117
|
+
*/
|
|
118
|
+
type CmsCollectionFile = {
|
|
119
|
+
name: string;
|
|
120
|
+
file: string;
|
|
121
|
+
fields: readonly CmsField[];
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Converts an array of `CmsField` definitions into a TypeScript object type,
|
|
125
|
+
* mapping each field's `name` to its corresponding TypeScript type via
|
|
126
|
+
* {@link TypeScriptTypeForField}.
|
|
127
|
+
*
|
|
128
|
+
* Fields marked as optional (via {@link OptionalWidget}) are emitted as
|
|
129
|
+
* optional keys (`key?`); all other fields are required.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* type Result = FieldsToObject<[
|
|
133
|
+
* { name: "title"; widget: "string" },
|
|
134
|
+
* { name: "ending"; widget: "string"; required: false; __optional: true },
|
|
135
|
+
* ]>;
|
|
136
|
+
* // { ending?: string } & { title: string }
|
|
137
|
+
*/
|
|
138
|
+
type FieldsToObject<Fields extends readonly CmsField[]> = { [F in Fields[number] as F extends OptionalWidget<F> ? F['name'] : never]?: TypeScriptTypeForField<F> } & { [F in Fields[number] as F extends OptionalWidget<F> ? never : F['name']]: TypeScriptTypeForField<F> };
|
|
139
|
+
/**
|
|
140
|
+
* Translates the top-level `fields` of a CMS collection file definition into a
|
|
141
|
+
* TypeScript object type using {@link FieldsToObject}.
|
|
142
|
+
*
|
|
143
|
+
* This is the entry point for type generation from a file definition — it
|
|
144
|
+
* extracts `F["fields"]` and delegates to `FieldsToObject` so that the same
|
|
145
|
+
* optional/required splitting logic applies uniformly.
|
|
146
|
+
*/
|
|
147
|
+
type FieldsOfFile<F extends CmsCollectionFile> = FieldsToObject<F['fields']>;
|
|
148
|
+
/**
|
|
149
|
+
* Translate from `widget` types to their corresponding TypeScript types. For
|
|
150
|
+
* example, a field with `widget: "string"` will be translated to a TypeScript
|
|
151
|
+
* type of `string`, and a field with `widget: "object"` and nested `fields`
|
|
152
|
+
* will be recursively translated to a nested object type.
|
|
153
|
+
*
|
|
154
|
+
* For `widget: "select"`, if the field's `options` is a readonly tuple of
|
|
155
|
+
* string literals, the type is narrowed to the union of those literals instead
|
|
156
|
+
* of a plain `string`.
|
|
157
|
+
*
|
|
158
|
+
* Note: This is not an exhaustive mapping of all possible widget types, but it
|
|
159
|
+
* can be extended as needed.
|
|
160
|
+
*/
|
|
161
|
+
type TypeScriptTypeForField<F extends CmsField> = F['widget'] extends 'string' | 'richtext' | 'text' | 'image' ? string : F['widget'] extends 'number' ? number : F['widget'] extends 'boolean' ? boolean : F['widget'] extends 'select' ? F extends {
|
|
162
|
+
options: readonly (infer O)[];
|
|
163
|
+
} ? O : string : F['widget'] extends 'list' ? F extends {
|
|
164
|
+
fields: readonly CmsField[];
|
|
165
|
+
} ? Array<FieldsToObject<F['fields']>> : F extends {
|
|
166
|
+
field: CmsField;
|
|
167
|
+
} ? Array<TypeScriptTypeForField<F['field']>> : unknown[] : F['widget'] extends 'object' ? F extends {
|
|
168
|
+
fields: readonly CmsField[];
|
|
169
|
+
} ? FieldsToObject<F['fields']> : Record<string, unknown> : unknown;
|
|
170
|
+
/**
|
|
171
|
+
* Get all the file names for a given collection name as a union of string
|
|
172
|
+
* literal types to later use with `Lookup<...>`. For example,
|
|
173
|
+
* `CollectionItemNames<Config, "pages">` would resolve to the union of all
|
|
174
|
+
* file names defined in the "Pages" collection, such as `"home" | "about" | ...`.
|
|
175
|
+
*/
|
|
176
|
+
type CollectionItemNames<TConfig extends {
|
|
177
|
+
collections: readonly unknown[];
|
|
178
|
+
}, C extends CollectionNames<TConfig>> = CollectionByName<TConfig, C> extends {
|
|
179
|
+
files: readonly unknown[];
|
|
180
|
+
} ? CollectionByName<TConfig, C>['files'][number] extends {
|
|
181
|
+
name: infer N;
|
|
182
|
+
} ? N & string : never : never;
|
|
183
|
+
/**
|
|
184
|
+
* Based on a collection's name and the respective file name, return a TypeScript
|
|
185
|
+
* object presenting the translated DecapCMS widget types into native TypeScript
|
|
186
|
+
* types.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* import { config } from '@/cms/config';
|
|
190
|
+
* type MyProps = PropsByCollectionAndFile<typeof config, 'blog', 'post'>;
|
|
191
|
+
*/
|
|
192
|
+
type PropsByCollectionAndFile<TConfig extends {
|
|
193
|
+
collections: readonly unknown[];
|
|
194
|
+
}, C extends CollectionNames<TConfig>, F extends CollectionItemNames<TConfig, C>> = IDE_HACK_ExpandRecursiveType<CollectionByName<TConfig, C> extends {
|
|
195
|
+
files: infer Files extends readonly unknown[];
|
|
196
|
+
} ? FieldsOfFile<FileByName<{
|
|
197
|
+
files: Files;
|
|
198
|
+
}, F & string>> : never>;
|
|
199
|
+
type WidgetFactory = (...args: any[]) => CmsField;
|
|
200
|
+
/**
|
|
201
|
+
* Given a widget factory function, return the TypeScript type that corresponds
|
|
202
|
+
* to the `widget` type(s) that the factory produces. For example, if a widget
|
|
203
|
+
* factory produces a field with `widget: "object"` and nested `fields`, it will
|
|
204
|
+
* be translated to a nested object type.
|
|
205
|
+
*
|
|
206
|
+
* Note: This relies on the widget factories being defined in a way that their
|
|
207
|
+
* return type is fully inferable by TypeScript.
|
|
208
|
+
*/
|
|
209
|
+
type WidgetTypeFromFactory<F extends WidgetFactory> = IDE_HACK_ExpandRecursiveType<TypeScriptTypeForField<ReturnType<F>>>;
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/widgets.d.ts
|
|
212
|
+
declare function optional<T extends CmsField>(widget: T): OptionalWidget<T>;
|
|
213
|
+
declare function boolWidget<T extends string>(label: string, name: T, options?: WidgetOpts<CmsFieldBoolean>): {
|
|
214
|
+
readonly default?: boolean;
|
|
215
|
+
readonly required?: boolean;
|
|
216
|
+
readonly hint?: string;
|
|
217
|
+
readonly pattern?: [string, string];
|
|
218
|
+
readonly label: string;
|
|
219
|
+
readonly name: T;
|
|
220
|
+
readonly widget: "boolean";
|
|
221
|
+
};
|
|
222
|
+
declare function imageWidget<T extends string>(label: string, name: T, options?: WidgetOpts<CmsFieldFileOrImage>): {
|
|
223
|
+
readonly media_library: {
|
|
224
|
+
readonly name: "cloudinary";
|
|
225
|
+
readonly config: {
|
|
226
|
+
readonly default_transformations: readonly [readonly [{
|
|
227
|
+
readonly fetch_format: "auto";
|
|
228
|
+
readonly width: 300;
|
|
229
|
+
readonly quality: "auto";
|
|
230
|
+
readonly crop: "scale";
|
|
231
|
+
}]];
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
readonly default?: string;
|
|
235
|
+
readonly allow_multiple?: boolean;
|
|
236
|
+
readonly choose_url?: boolean;
|
|
237
|
+
readonly config?: any;
|
|
238
|
+
readonly required?: boolean;
|
|
239
|
+
readonly hint?: string;
|
|
240
|
+
readonly pattern?: [string, string];
|
|
241
|
+
readonly label: string;
|
|
242
|
+
readonly name: T;
|
|
243
|
+
readonly widget: "image";
|
|
244
|
+
};
|
|
245
|
+
declare function listWidget<T extends string, F extends CmsField>(label: string, name: T, fields: F, options?: WidgetOpts<CmsFieldList>): {
|
|
246
|
+
label: string;
|
|
247
|
+
name: T;
|
|
248
|
+
widget: 'list';
|
|
249
|
+
field: F;
|
|
250
|
+
};
|
|
251
|
+
declare function listWidget<T extends string, F extends CmsField[]>(label: string, name: T, fields: F, options?: WidgetOpts<CmsFieldList>): {
|
|
252
|
+
label: string;
|
|
253
|
+
name: T;
|
|
254
|
+
widget: 'list';
|
|
255
|
+
fields: F;
|
|
256
|
+
};
|
|
257
|
+
declare function markdownWidget<T extends string>(label: string, name: T, options?: WidgetOpts<CmsFieldMarkdown>): {
|
|
258
|
+
readonly default?: string;
|
|
259
|
+
readonly minimal?: boolean;
|
|
260
|
+
readonly buttons?: import("decap-cms-core").CmsMarkdownWidgetButton[];
|
|
261
|
+
readonly editor_components?: string[];
|
|
262
|
+
readonly modes?: ("raw" | "rich_text")[];
|
|
263
|
+
readonly editorComponents?: string[];
|
|
264
|
+
readonly required?: boolean;
|
|
265
|
+
readonly hint?: string;
|
|
266
|
+
readonly pattern?: [string, string];
|
|
267
|
+
readonly label: string;
|
|
268
|
+
readonly name: T;
|
|
269
|
+
readonly widget: "string";
|
|
270
|
+
};
|
|
271
|
+
declare function objectWidget<T extends string, F extends CmsField[]>(label: string, name: T, fields: F, options?: WidgetOpts<CmsFieldObject>): {
|
|
272
|
+
readonly fields: F;
|
|
273
|
+
readonly default?: any;
|
|
274
|
+
readonly collapsed?: boolean;
|
|
275
|
+
readonly summary?: string;
|
|
276
|
+
readonly required?: boolean;
|
|
277
|
+
readonly hint?: string;
|
|
278
|
+
readonly pattern?: [string, string];
|
|
279
|
+
readonly label: string;
|
|
280
|
+
readonly name: T;
|
|
281
|
+
readonly widget: "object";
|
|
282
|
+
};
|
|
283
|
+
declare function numberWidget<T extends string>(label: string, name: T, options?: WidgetOpts<CmsFieldNumber>): {
|
|
284
|
+
readonly default?: string | number;
|
|
285
|
+
readonly value_type?: "int" | "float" | string;
|
|
286
|
+
readonly min?: number;
|
|
287
|
+
readonly max?: number;
|
|
288
|
+
readonly step?: number;
|
|
289
|
+
readonly valueType?: "int" | "float" | string;
|
|
290
|
+
readonly required?: boolean;
|
|
291
|
+
readonly hint?: string;
|
|
292
|
+
readonly pattern?: [string, string];
|
|
293
|
+
readonly label: string;
|
|
294
|
+
readonly name: T;
|
|
295
|
+
readonly widget: "number";
|
|
296
|
+
};
|
|
297
|
+
declare function selectWidget<T extends string, O extends string[]>(label: string, name: T, choices: O, options?: WidgetOpts<CmsFieldSelect>): {
|
|
298
|
+
readonly options: O;
|
|
299
|
+
readonly default?: string | string[];
|
|
300
|
+
readonly min?: number;
|
|
301
|
+
readonly max?: number;
|
|
302
|
+
readonly multiple?: boolean;
|
|
303
|
+
readonly required?: boolean;
|
|
304
|
+
readonly hint?: string;
|
|
305
|
+
readonly pattern?: [string, string];
|
|
306
|
+
readonly label: string;
|
|
307
|
+
readonly name: T;
|
|
308
|
+
readonly widget: "select";
|
|
309
|
+
};
|
|
310
|
+
declare function stringWidget<T extends string>(label: string, name: T, options?: WidgetOpts<CmsFieldStringOrText>): {
|
|
311
|
+
readonly default?: string;
|
|
312
|
+
readonly visualEditing?: boolean;
|
|
313
|
+
readonly required?: boolean;
|
|
314
|
+
readonly hint?: string;
|
|
315
|
+
readonly pattern?: [string, string];
|
|
316
|
+
readonly label: string;
|
|
317
|
+
readonly name: T;
|
|
318
|
+
readonly widget: "string";
|
|
319
|
+
};
|
|
320
|
+
declare function textWidget<T extends string>(label: string, name: T, options?: WidgetOpts<CmsFieldStringOrText>): {
|
|
321
|
+
readonly default?: string;
|
|
322
|
+
readonly visualEditing?: boolean;
|
|
323
|
+
readonly required?: boolean;
|
|
324
|
+
readonly hint?: string;
|
|
325
|
+
readonly pattern?: [string, string];
|
|
326
|
+
readonly label: string;
|
|
327
|
+
readonly name: T;
|
|
328
|
+
readonly widget: "text";
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* Stripped down Markdown widget configuration that does the following:
|
|
332
|
+
*
|
|
333
|
+
* - Allows bold, italic, lists (bullet + numbered), quotes
|
|
334
|
+
* - No images or code block components
|
|
335
|
+
* - Rich Text preview only
|
|
336
|
+
*/
|
|
337
|
+
declare const BARE_MARKDOWN: WidgetOpts<CmsFieldMarkdown>;
|
|
338
|
+
/**
|
|
339
|
+
* Stripped down Markdown Widget configuration that allows only inline
|
|
340
|
+
* elements (e.g., no lists, quotes, or headings) and rich text preview only.
|
|
341
|
+
*/
|
|
342
|
+
declare const INLINE_MARKDOWN: WidgetOpts<CmsFieldMarkdown>;
|
|
343
|
+
//#endregion
|
|
344
|
+
export { BARE_MARKDOWN, INLINE_MARKDOWN, type OptionalWidget, type PropsByCollectionAndFile, type WidgetOpts, type WidgetTypeFromFactory, boolWidget, imageWidget, listWidget, markdownWidget, numberWidget, objectWidget, optional, selectWidget, stringWidget, textWidget };
|
|
345
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/api.ts","../src/widgets.ts"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
//#region src/widgets.ts
|
|
2
|
+
function optional(widget) {
|
|
3
|
+
return {
|
|
4
|
+
...widget,
|
|
5
|
+
required: false,
|
|
6
|
+
__optional: true
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function boolWidget(label, name, options) {
|
|
10
|
+
return {
|
|
11
|
+
label,
|
|
12
|
+
name,
|
|
13
|
+
widget: "boolean",
|
|
14
|
+
...options ?? {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function imageWidget(label, name, options) {
|
|
18
|
+
return {
|
|
19
|
+
label,
|
|
20
|
+
name,
|
|
21
|
+
widget: "image",
|
|
22
|
+
...options ?? {},
|
|
23
|
+
media_library: {
|
|
24
|
+
name: "cloudinary",
|
|
25
|
+
config: { default_transformations: [[{
|
|
26
|
+
fetch_format: "auto",
|
|
27
|
+
width: 300,
|
|
28
|
+
quality: "auto",
|
|
29
|
+
crop: "scale"
|
|
30
|
+
}]] }
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function listWidget(label, name, fields, options) {
|
|
35
|
+
return {
|
|
36
|
+
label,
|
|
37
|
+
name,
|
|
38
|
+
widget: "list",
|
|
39
|
+
...options ?? {},
|
|
40
|
+
...Array.isArray(fields) ? { fields } : { field: fields }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function markdownWidget(label, name, options) {
|
|
44
|
+
return {
|
|
45
|
+
label,
|
|
46
|
+
name,
|
|
47
|
+
widget: "richtext",
|
|
48
|
+
...options ?? {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function objectWidget(label, name, fields, options) {
|
|
52
|
+
return {
|
|
53
|
+
label,
|
|
54
|
+
name,
|
|
55
|
+
widget: "object",
|
|
56
|
+
...options ?? {},
|
|
57
|
+
fields
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function numberWidget(label, name, options) {
|
|
61
|
+
return {
|
|
62
|
+
label,
|
|
63
|
+
name,
|
|
64
|
+
widget: "number",
|
|
65
|
+
...options ?? {}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function selectWidget(label, name, choices, options) {
|
|
69
|
+
return {
|
|
70
|
+
label,
|
|
71
|
+
name,
|
|
72
|
+
widget: "select",
|
|
73
|
+
...options ?? {},
|
|
74
|
+
options: choices ?? []
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function stringWidget(label, name, options) {
|
|
78
|
+
return {
|
|
79
|
+
label,
|
|
80
|
+
name,
|
|
81
|
+
widget: "string",
|
|
82
|
+
...options ?? {}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function textWidget(label, name, options) {
|
|
86
|
+
return {
|
|
87
|
+
label,
|
|
88
|
+
name,
|
|
89
|
+
widget: "text",
|
|
90
|
+
...options ?? {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Stripped down Markdown widget configuration that does the following:
|
|
95
|
+
*
|
|
96
|
+
* - Allows bold, italic, lists (bullet + numbered), quotes
|
|
97
|
+
* - No images or code block components
|
|
98
|
+
* - Rich Text preview only
|
|
99
|
+
*/
|
|
100
|
+
const BARE_MARKDOWN = {
|
|
101
|
+
buttons: [
|
|
102
|
+
"bold",
|
|
103
|
+
"bulleted-list",
|
|
104
|
+
"italic",
|
|
105
|
+
"link",
|
|
106
|
+
"numbered-list",
|
|
107
|
+
"quote"
|
|
108
|
+
],
|
|
109
|
+
editor_components: [],
|
|
110
|
+
modes: ["rich_text"]
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Stripped down Markdown Widget configuration that allows only inline
|
|
114
|
+
* elements (e.g., no lists, quotes, or headings) and rich text preview only.
|
|
115
|
+
*/
|
|
116
|
+
const INLINE_MARKDOWN = {
|
|
117
|
+
buttons: [
|
|
118
|
+
"bold",
|
|
119
|
+
"italic",
|
|
120
|
+
"link"
|
|
121
|
+
],
|
|
122
|
+
editor_components: [],
|
|
123
|
+
modes: ["rich_text"]
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { BARE_MARKDOWN, INLINE_MARKDOWN, boolWidget, imageWidget, listWidget, markdownWidget, numberWidget, objectWidget, optional, selectWidget, stringWidget, textWidget };
|
|
128
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/widgets.ts"],"sourcesContent":["import type {\n\tCmsField,\n\tCmsFieldBoolean,\n\tCmsFieldFileOrImage,\n\tCmsFieldList,\n\tCmsFieldMarkdown,\n\tCmsFieldNumber,\n\tCmsFieldObject,\n\tCmsFieldSelect,\n\tCmsFieldStringOrText,\n} from 'decap-cms-core';\n\nimport type { OptionalWidget, WidgetOpts } from './api.js';\n\n// Widget Modifiers\n\nexport function optional<T extends CmsField>(widget: T): OptionalWidget<T> {\n\treturn {\n\t\t...widget,\n\t\trequired: false as const,\n\t\t__optional: true as const,\n\t};\n}\n\n// Primitive Widgets\n\nexport function boolWidget<T extends string>(\n\tlabel: string,\n\tname: T,\n\toptions?: WidgetOpts<CmsFieldBoolean>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'boolean',\n\t\t...(options ?? {}),\n\t} as const satisfies CmsField;\n}\n\nexport function imageWidget<T extends string>(\n\tlabel: string,\n\tname: T,\n\toptions?: WidgetOpts<CmsFieldFileOrImage>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'image',\n\t\t...(options ?? {}),\n\t\tmedia_library: {\n\t\t\tname: 'cloudinary',\n\t\t\tconfig: {\n\t\t\t\tdefault_transformations: [\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfetch_format: 'auto',\n\t\t\t\t\t\t\twidth: 300,\n\t\t\t\t\t\t\tquality: 'auto',\n\t\t\t\t\t\t\tcrop: 'scale',\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t} as const satisfies CmsField;\n}\n\nexport function listWidget<T extends string, F extends CmsField>(\n\tlabel: string,\n\tname: T,\n\tfields: F,\n\toptions?: WidgetOpts<CmsFieldList>,\n): { label: string; name: T; widget: 'list'; field: F };\nexport function listWidget<T extends string, F extends CmsField[]>(\n\tlabel: string,\n\tname: T,\n\tfields: F,\n\toptions?: WidgetOpts<CmsFieldList>,\n): { label: string; name: T; widget: 'list'; fields: F };\nexport function listWidget<T extends string, F extends CmsField | CmsField[]>(\n\tlabel: string,\n\tname: T,\n\tfields: F,\n\toptions?: WidgetOpts<CmsFieldList>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'list',\n\t\t...(options ?? {}),\n\t\t...(Array.isArray(fields) ? { fields } : { field: fields }),\n\t} as const;\n}\n\nexport function markdownWidget<T extends string>(\n\tlabel: string,\n\tname: T,\n\toptions?: WidgetOpts<CmsFieldMarkdown>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'richtext' as 'string',\n\t\t...(options ?? {}),\n\t} as const satisfies CmsField;\n}\n\nexport function objectWidget<T extends string, F extends CmsField[]>(\n\tlabel: string,\n\tname: T,\n\tfields: F,\n\toptions?: WidgetOpts<CmsFieldObject>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'object',\n\t\t...(options ?? {}),\n\t\tfields,\n\t} as const satisfies CmsField;\n}\n\nexport function numberWidget<T extends string>(\n\tlabel: string,\n\tname: T,\n\toptions?: WidgetOpts<CmsFieldNumber>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'number',\n\t\t...(options ?? {}),\n\t} as const satisfies CmsField;\n}\n\nexport function selectWidget<T extends string, O extends string[]>(\n\tlabel: string,\n\tname: T,\n\tchoices: O,\n\toptions?: WidgetOpts<CmsFieldSelect>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'select',\n\t\t...(options ?? {}),\n\t\toptions: choices ?? [],\n\t} as const satisfies CmsField;\n}\n\nexport function stringWidget<T extends string>(\n\tlabel: string,\n\tname: T,\n\toptions?: WidgetOpts<CmsFieldStringOrText>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'string',\n\t\t...(options ?? {}),\n\t} as const satisfies CmsField;\n}\n\nexport function textWidget<T extends string>(\n\tlabel: string,\n\tname: T,\n\toptions?: WidgetOpts<CmsFieldStringOrText>,\n) {\n\treturn {\n\t\tlabel,\n\t\tname,\n\t\twidget: 'text',\n\t\t...(options ?? {}),\n\t} as const satisfies CmsField;\n}\n\n// Reusable Widget Configurations\n\n/**\n * Stripped down Markdown widget configuration that does the following:\n *\n * - Allows bold, italic, lists (bullet + numbered), quotes\n * - No images or code block components\n * - Rich Text preview only\n */\nexport const BARE_MARKDOWN: WidgetOpts<CmsFieldMarkdown> = {\n\tbuttons: [\n\t\t'bold',\n\t\t'bulleted-list',\n\t\t'italic',\n\t\t'link',\n\t\t'numbered-list',\n\t\t'quote',\n\t],\n\teditor_components: [],\n\tmodes: ['rich_text'],\n};\n\n/**\n * Stripped down Markdown Widget configuration that allows only inline\n * elements (e.g., no lists, quotes, or headings) and rich text preview only.\n */\nexport const INLINE_MARKDOWN: WidgetOpts<CmsFieldMarkdown> = {\n\tbuttons: ['bold', 'italic', 'link'],\n\teditor_components: [],\n\tmodes: ['rich_text'],\n};\n"],"mappings":";AAgBA,SAAgB,SAA6B,QAA8B;CAC1E,OAAO;EACN,GAAG;EACH,UAAU;EACV,YAAY;CACb;AACD;AAIA,SAAgB,WACf,OACA,MACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;CACjB;AACD;AAEA,SAAgB,YACf,OACA,MACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;EAChB,eAAe;GACd,MAAM;GACN,QAAQ,EACP,yBAAyB,CACxB,CACC;IACC,cAAc;IACd,OAAO;IACP,SAAS;IACT,MAAM;GACP,CACD,CACD,EACD;EACD;CACD;AACD;AAcA,SAAgB,WACf,OACA,MACA,QACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;EAChB,GAAI,MAAM,QAAQ,MAAM,IAAI,EAAE,OAAO,IAAI,EAAE,OAAO,OAAO;CAC1D;AACD;AAEA,SAAgB,eACf,OACA,MACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;CACjB;AACD;AAEA,SAAgB,aACf,OACA,MACA,QACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;EAChB;CACD;AACD;AAEA,SAAgB,aACf,OACA,MACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;CACjB;AACD;AAEA,SAAgB,aACf,OACA,MACA,SACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;EAChB,SAAS,WAAW,CAAC;CACtB;AACD;AAEA,SAAgB,aACf,OACA,MACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;CACjB;AACD;AAEA,SAAgB,WACf,OACA,MACA,SACC;CACD,OAAO;EACN;EACA;EACA,QAAQ;EACR,GAAI,WAAW,CAAC;CACjB;AACD;;;;;;;;AAWA,MAAa,gBAA8C;CAC1D,SAAS;EACR;EACA;EACA;EACA;EACA;EACA;CACD;CACA,mBAAmB,CAAC;CACpB,OAAO,CAAC,WAAW;AACpB;;;;;AAMA,MAAa,kBAAgD;CAC5D,SAAS;EAAC;EAAQ;EAAU;CAAM;CAClC,mBAAmB,CAAC;CACpB,OAAO,CAAC,WAAW;AACpB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@allejo/decap-extras",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A TypeScript utility library for Decap CMS with utility types to derive types from Decap CMS configuration files.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"decapcms"
|
|
7
|
+
],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/allejo/decap-extras"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Vladimir \"allejo\" Jimenez",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.mjs",
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsdown",
|
|
19
|
+
"format": "prettier --write .",
|
|
20
|
+
"test": "vitest run --typecheck"
|
|
21
|
+
},
|
|
22
|
+
"lint-staged": {
|
|
23
|
+
"*.{md,ts}": "prettier --write",
|
|
24
|
+
"*.ts": "eslint --fix"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@allejo/prettier-config": "^1.0.8",
|
|
28
|
+
"@commitlint/cli": "^18.6.0",
|
|
29
|
+
"@commitlint/config-conventional": "^18.6.0",
|
|
30
|
+
"@commitlint/types": "^21.0.1",
|
|
31
|
+
"@types/eslint": "^8.44.7",
|
|
32
|
+
"@types/node": "^24.12.4",
|
|
33
|
+
"decap-cms-app": "^3.12.2",
|
|
34
|
+
"eslint": "^9.21.0",
|
|
35
|
+
"husky": "^9.0.10",
|
|
36
|
+
"lint-staged": "^15.4.3",
|
|
37
|
+
"prettier": "^3.1.1",
|
|
38
|
+
"tsdown": "^0.22.0",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"vitest": "^3.2.4"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"decap-cms-core": "^3.12.2"
|
|
44
|
+
}
|
|
45
|
+
}
|