From 6c1e45f9d2142dd0211224aa109d304ae2fc50f7 Mon Sep 17 00:00:00 2001 From: Alex Rock Ancelet Date: Tue, 13 Feb 2024 16:26:27 +0100 Subject: [PATCH] Add lots of documentation directly in types --- .github/workflows/pages.yaml | 15 ++-- README.md | 6 +- {docs => docs-src}/backoffice_edit.png | Bin {docs => docs-src}/backoffice_list.png | Bin {docs => docs-src}/backoffice_view.png | Bin package.json | 8 +- src/lib/Crud/Operations.ts | 90 +++++++++++++++++---- src/lib/Crud/definition.ts | 52 +++++++++--- src/lib/Crud/form.ts | 3 + src/lib/Dashboard/definition.ts | 33 ++++++-- src/lib/DataTable/Pagination.ts | 7 +- src/lib/FieldDefinitions/Checkbox.ts | 2 + src/lib/FieldDefinitions/Columns.ts | 21 +++-- src/lib/FieldDefinitions/CrudEntity.ts | 2 + src/lib/FieldDefinitions/Date.ts | 2 + src/lib/FieldDefinitions/KeyValueObject.ts | 2 + src/lib/FieldDefinitions/Number.ts | 2 + src/lib/FieldDefinitions/Tabs.ts | 3 + src/lib/FieldDefinitions/Text.ts | 2 + src/lib/FieldDefinitions/Textarea.ts | 2 + src/lib/FieldDefinitions/Toggle.ts | 2 + src/lib/FieldDefinitions/Url.ts | 2 + src/lib/FieldDefinitions/definition.ts | 15 +++- src/lib/Filter/index.ts | 8 ++ src/lib/Menu/MenuLinks.ts | 3 + src/lib/Menu/stores.ts | 1 + src/lib/State/Processor.ts | 4 + src/lib/State/Provider.ts | 4 + src/lib/actions.ts | 8 ++ src/lib/admin_i18n.ts | 4 + src/lib/config/adminConfig.ts | 1 + src/lib/config/types.ts | 1 + src/lib/genericTypes.ts | 2 + src/lib/messages/messages.ts | 7 ++ src/lib/request.ts | 1 + src/lib/themes/ThemeConfig.ts | 11 +-- src/testApp/Dashboard.ts | 4 + typedoc.json | 14 ++++ yarn.lock | 64 ++++++++++++++- 39 files changed, 349 insertions(+), 59 deletions(-) rename {docs => docs-src}/backoffice_edit.png (100%) rename {docs => docs-src}/backoffice_list.png (100%) rename {docs => docs-src}/backoffice_view.png (100%) create mode 100644 typedoc.json diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 4a081d57..065fe6eb 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -31,20 +31,25 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} + - name: 🟢 Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: 'yarn' - - run: yarn install --frozen-lockfile + - name: 🧰 Install dependencies + run: yarn install --frozen-lockfile - - run: yarn run build + - name: 🔨 Build demo app + run: yarn run build - - name: Prepare Github Pages + - name: 📄 Build types documentation + run: yarn run typedoc + + - name: 🌐 Configure Github Pages domain run: echo "svelte-admin-demo.orbitale.io" > build/CNAME - - name: Deploy + - name: 🚀 Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index c1dc0e50..3f3bb375 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ This package is an **admin generator** for your JS/TS/Svelte projects. It can consume a distant API, a localStorage, or even an RPC-based data storage (like when using [Tauri](https://tauri.app/)). -| List | View | Edit | -| ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | -| ![SvelteAdmin Backoffice List](./docs/backoffice_list.png) | ![SvelteAdmin Backoffice View](./docs/backoffice_view.png) | ![SvelteAdmin Backoffice Edit](./docs/backoffice_edit.png) | +| List | View | Edit | +|----------------------------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------| +| ![SvelteAdmin Backoffice List](./docs-src/backoffice_list.png) | ![SvelteAdmin Backoffice View](./docs-src/backoffice_view.png) | ![SvelteAdmin Backoffice Edit](./docs-src/backoffice_edit.png) | There is a [roadmap](#roadmap) at the end of this documentation to know what features are soon coming! diff --git a/docs/backoffice_edit.png b/docs-src/backoffice_edit.png similarity index 100% rename from docs/backoffice_edit.png rename to docs-src/backoffice_edit.png diff --git a/docs/backoffice_list.png b/docs-src/backoffice_list.png similarity index 100% rename from docs/backoffice_list.png rename to docs-src/backoffice_list.png diff --git a/docs/backoffice_view.png b/docs-src/backoffice_view.png similarity index 100% rename from docs/backoffice_view.png rename to docs-src/backoffice_view.png diff --git a/package.json b/package.json index cfc82ac0..e3f725e8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --plugin=prettier-plugin-svelte . --check . && eslint src", "format": "prettier --plugin=prettier-plugin-svelte --write .", - "test:unit": "vitest" + "test:unit": "vitest", + "typedoc": "typedoc --options typedoc.json --readme none" }, "exports": { ".": { @@ -47,7 +48,8 @@ "carbon-icons-svelte": "^12.0.0", "luxon": "^3.4.4", "svelte": "^4.0.0", - "svelte-i18n": "^4.0.0" + "svelte-i18n": "^4.0.0", + "typedoc": "^0.25.8" }, "devDependencies": { "@faker-js/faker": "^8.3.1", @@ -56,6 +58,7 @@ "@sveltejs/kit": "^2.0.0", "@sveltejs/package": "^2.2.3", "@sveltejs/vite-plugin-svelte": "^3.0.1", + "@types/node": "^20.11.17", "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", @@ -71,6 +74,7 @@ "sass": "^1.69.5", "svelte-check": "^3.6.2", "tslib": "^2.4.1", + "typedoc-plugin-mdn-links": "^3.1.16", "typescript": "^5.3.3", "vite": "^5.0.7", "vitest": "^1.0.4" diff --git a/src/lib/Crud/Operations.ts b/src/lib/Crud/Operations.ts index 76a52973..8a610e89 100644 --- a/src/lib/Crud/Operations.ts +++ b/src/lib/Crud/Operations.ts @@ -1,3 +1,8 @@ +/** + * Operations are a class system that allows you to determine what grids or forms you will display in your {@link Dashboard.DashboardDefinition | Dashboard} + * @module + */ + import type { FieldOptions, FieldInterface } from '$lib/FieldDefinitions/definition'; import type { CrudDefinition } from '$lib/Crud/definition'; import type { DashboardDefinition } from '$lib/Dashboard/definition'; @@ -6,6 +11,7 @@ import type { CrudTheme } from '$lib/themes/ThemeConfig'; import { defaultPaginationOptions, type PaginationOptions } from '$lib/DataTable/Pagination'; import type { FilterInterface, FilterOptions } from '$lib/Filter'; +/** */ export type CrudOperationName = | 'new' | 'edit' @@ -16,33 +22,57 @@ export type CrudOperationName = | 'entity_list' | string; +/** */ export interface CrudOperation { - readonly name: CrudOperationName; - readonly label: string; - readonly displayComponentName: CrudTheme; - readonly fields: Array>; - readonly actions: Array; - readonly options: Record; - + /** */ readonly name: CrudOperationName; + /** */ readonly label: string; + /** */ readonly displayComponentName: CrudTheme; + /** */ readonly fields: Array>; + /** */ readonly actions: Array; + /** */ readonly options: Record; + + /** */ get dashboard(): DashboardDefinition; set dashboard(dashboard: DashboardDefinition); + + /** */ get crud(): CrudDefinition; set crud(crud: CrudDefinition); } -export class BaseCrudOperation implements CrudOperation { +/** + * @abstract + * + * @remark + * This class allows you to create your own classes and extend the base operation + * in case you need something else than the built-in ones. + * + * @example + * export class PreviewOperation extends BaseCrudOperation { + * // Your custom code + * constructor( + * fields: Array>, + * actions: Array = [], + * options: FormOperationOptions = DEFAULT_FORM_OPERATION_OPTION + * ) { + * super('preview', 'crud.preview.label', 'preview', fields, actions, options); + * } + * } + **/ +export abstract class BaseCrudOperation implements CrudOperation { private _dashboard: DashboardDefinition | null = null; private _crud: CrudDefinition | null = null; - constructor( - public readonly name: CrudOperationName, - public readonly label: string, - public readonly displayComponentName: CrudTheme, - public readonly fields: Array>, - public readonly actions: Array, - public readonly options: Record = {} + protected constructor( + /** */ public readonly name: CrudOperationName, + /** */ public readonly label: string, + /** */ public readonly displayComponentName: CrudTheme, + /** */ public readonly fields: Array>, + /** */ public readonly actions: Array, + /** */ public readonly options: Record = {} ) {} + /** */ get dashboard(): DashboardDefinition { if (!this._dashboard) { throw new Error('Dashboard is not set in operation: did you try to bypass Crud setup?'); @@ -60,6 +90,7 @@ export class BaseCrudOperation implements CrudOperation { this._dashboard = dashboard; } + /** */ get crud(): CrudDefinition { if (!this._crud) { throw new Error('Crud is not set in operation: did you try to bypass Crud setup?'); @@ -75,14 +106,24 @@ export class BaseCrudOperation implements CrudOperation { } } +/** + * @see {@link New} + * @see {@link Edit} + **/ export type FormOperationOptions = object & { preventHttpFormSubmit: boolean; }; +/** */ const DEFAULT_FORM_OPERATION_OPTION: FormOperationOptions = { preventHttpFormSubmit: true }; +/** + * @group Built-in operations + * @category Built-in operations + */ export class New extends BaseCrudOperation { + /** */ constructor( fields: Array>, actions: Array = [], @@ -92,7 +133,10 @@ export class New extends BaseCrudOperation { } } +/** + */ export class Edit extends BaseCrudOperation { + /** */ constructor( fields: Array>, actions: Array = [], @@ -102,13 +146,19 @@ export class Edit extends BaseCrudOperation { } } +/** + * @see {@link List} + **/ export type ListOperationOptions = object & { globalActions?: Array; pagination?: Partial; filters?: FilterInterface[]; }; +/** + */ export class List extends BaseCrudOperation { + /** */ constructor( fields: Array>, actions: Array = [], @@ -120,22 +170,32 @@ export class List extends BaseCrudOperation { } } +/** + */ export class Delete extends BaseCrudOperation { + /** */ public readonly redirectTo: Action; + /** */ constructor(fields: Array>, redirectTo: Action) { super('delete', 'crud.delete.label', 'delete', fields, []); this.redirectTo = redirectTo; } } +/** + */ export class View extends BaseCrudOperation { + /** */ constructor(fields: Array>) { super('view', 'crud.view.label', 'view', fields, []); } } +/** + */ export class Field extends BaseCrudOperation { + /** */ constructor(name: CrudOperationName = 'field', options: Record = {}) { super(name, '', 'field', [], [], options); } diff --git a/src/lib/Crud/definition.ts b/src/lib/Crud/definition.ts index bd3c35f5..1c97f500 100644 --- a/src/lib/Crud/definition.ts +++ b/src/lib/Crud/definition.ts @@ -3,7 +3,8 @@ import type { StateProvider } from '$lib/State/Provider'; import type { StateProcessor } from '$lib/State/Processor'; import { type DashboardDefinition } from '$lib/Dashboard/definition'; -export type CrudDefinitionOptionsArgument = { +/** */ +export type CrudDefinitionOptionsArgument = { name: string; label: { singular: string; @@ -11,21 +12,53 @@ export type CrudDefinitionOptionsArgument = { }; defaultOperationName?: string; identifierFieldName?: 'id' | string; + + /** + * Will apply a default minimum timeout (in milliseconds) when running a {@link StateProvider} or {@link StateProcessor}. + * If this option is defined, and the provider or processor call's duration is below this value, it will still wait this amount of time before returning the actual provider/processor value. + * + * The goal of this is to avoid epilepsy-like issues if providers or processors respond too quickly, especially when dealing with List operations and filters. + * This will create a "wait time" for the end user, so that the screen does not blink too much and there eyes (and brain) will not be stressed too much. + */ minStateLoadingTimeMs?: number; operations: Array; - stateProvider: StateProvider; - stateProcessor: StateProcessor; + stateProvider: StateProvider; + stateProcessor: StateProcessor; }; -export type CrudDefinitionOptions = Required>; +export type CrudDefinitionOptions = Required>; -export class CrudDefinition { - public readonly name: string; - public readonly options: CrudDefinitionOptions; +/** + * Crud definition, object used to create an abstract Crud. + * + * @remarks + * Crud objects are related to a single Entity type, + * and contain several Crud Operations, as well as the main + * objects that care about persistence: state providers and processors. + * + * @example + * type Book = {id: number, title: string, description: string}; + * + * const BooksCrud = new CrudDefinition({ + * name: 'books', + * label: {singular: 'Book', plural: 'Books'}, + * operations: [], + * stateProvider: ..., + * stateProcessor: ..., + * }); + * + * @typeParam EntityType - The object type that will be used by providers and processors. + */ +export class CrudDefinition { + /** */ public readonly name: string; + /** */ public readonly options: CrudDefinitionOptions; private _dashboard: DashboardDefinition | null = null; - constructor(options: CrudDefinitionOptionsArgument) { + /** + * @param {CrudDefinitionOptionsArgument} options + **/ + constructor(options: CrudDefinitionOptionsArgument) { const name = options.name; this.name = name; @@ -57,9 +90,10 @@ export class CrudDefinition { options.defaultOperationName = defaultOperation.name; options.identifierFieldName ??= 'id'; - this.options = options as CrudDefinitionOptions; + this.options = options as CrudDefinitionOptions; } + /** */ get dashboard(): DashboardDefinition { if (!this._dashboard) { throw new Error('Dashboard is not set in Crud definition: did you try to bypass Crud setup?'); diff --git a/src/lib/Crud/form.ts b/src/lib/Crud/form.ts index 9a2642bd..3fa766e1 100644 --- a/src/lib/Crud/form.ts +++ b/src/lib/Crud/form.ts @@ -1,5 +1,8 @@ export type SubmittedData = Record; +/** + * Function to get an record of {@link FormDataEntryValue} items from an "onSubmit" form {@link SubmitEvent} object. + */ export function getSubmittedFormData(event: SubmitEvent): SubmittedData { const normalizedData: SubmittedData = {}; diff --git a/src/lib/Dashboard/definition.ts b/src/lib/Dashboard/definition.ts index f71aae4c..b3d443d2 100644 --- a/src/lib/Dashboard/definition.ts +++ b/src/lib/Dashboard/definition.ts @@ -3,6 +3,8 @@ import type { Dictionaries } from '$lib/admin_i18n'; import type { CrudDefinition } from '$lib/Crud/definition'; import { type AdminConfig, emptyAdminConfig } from '$lib/config/adminConfig'; +/** + */ export type DashboardDefinitionOptions = { adminConfig: Partial; cruds: Array>; @@ -13,16 +15,34 @@ export type DashboardDefinitionOptions = { localeDictionaries?: Dictionaries; }; +/** + * @example + * export const dashboard = new DashboardDefinition({ + * admin: ..., // see AdminConfig + * + * // The main menu on the left side of the page + * sideMenu: [ + * new UrlAction('Homepage', '/', Home), + * new UrlAction('Book', '/admin/books/list', Book) + * ], + * + * // Here you set all the Crud configurations of your admin panel + * // For organization purposes, we recommend you to define your Crud configs + * // in separate typescript files, it makes it easier to read and maintain. + * cruds: [booksCrud] + * }); + */ export class DashboardDefinition { - public readonly adminConfig: AdminConfig; - public readonly cruds: Array>; - public readonly sideMenu: Array = []; - public readonly topLeftMenu: Array = []; - public readonly topRightMenu: Array = []; - public readonly localeDictionaries: Dictionaries = {}; + /** */ public readonly adminConfig: AdminConfig; + /** */ public readonly cruds: Array>; + /** */ public readonly sideMenu: Array = []; + /** */ public readonly topLeftMenu: Array = []; + /** */ public readonly topRightMenu: Array = []; + /** */ public readonly localeDictionaries: Dictionaries = {}; public readonly options = {}; + /** */ constructor(options: DashboardDefinitionOptions) { this.adminConfig = { ...emptyAdminConfig(), ...(options.adminConfig || {}) }; this.cruds = options.cruds; @@ -34,6 +54,7 @@ export class DashboardDefinition { this.checkUniqueCruds(); } + /** */ public getFirstActionUrl(): string { const firstCrud = this.cruds[0]; const firstOperation = firstCrud.options.operations[0]; diff --git a/src/lib/DataTable/Pagination.ts b/src/lib/DataTable/Pagination.ts index 2e4add53..16b8a524 100644 --- a/src/lib/DataTable/Pagination.ts +++ b/src/lib/DataTable/Pagination.ts @@ -1,17 +1,20 @@ -export class PaginatedResults { +/** */ +export class PaginatedResults { constructor( public readonly currentPage: number, public readonly numberOfPages: number, public readonly numberOfItems: number, - public readonly currentItems: Array + public readonly currentItems: Array ) {} } +/** */ export type PaginationOptions = { enabled: boolean; itemsPerPage: number; }; +/** */ export function defaultPaginationOptions(): PaginationOptions { return { enabled: true, diff --git a/src/lib/FieldDefinitions/Checkbox.ts b/src/lib/FieldDefinitions/Checkbox.ts index fc028a79..6998aca6 100644 --- a/src/lib/FieldDefinitions/Checkbox.ts +++ b/src/lib/FieldDefinitions/Checkbox.ts @@ -1,8 +1,10 @@ import { type CommonFieldOptions, Field } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type CheckboxOptions = CommonFieldOptions; +/** */ export class CheckboxField extends Field { readonly formComponent: FormFieldTheme = 'checkbox'; readonly viewComponent: ViewFieldTheme = 'checkbox'; diff --git a/src/lib/FieldDefinitions/Columns.ts b/src/lib/FieldDefinitions/Columns.ts index 1c4571d8..08ff32e7 100644 --- a/src/lib/FieldDefinitions/Columns.ts +++ b/src/lib/FieldDefinitions/Columns.ts @@ -1,24 +1,31 @@ import type { FieldInterface, FieldOptions } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; -/** - * The sizes are related to the grid used by your theme. - * Carbon theme has a dynamic grid up from 4 to 16 columns depending on viewport, - * while Bootstrap-based ones have 12 columns. - */ +/** */ export type ColumnOptions = FieldOptions & { name: string; label?: string; }; -export type ColumnedFields> = Array<{ +/** + * + * @remark + * The sizes and offsets are related to the grid used by your theme.
+ * For example, **Carbon** theme has a dynamic grid up from 4 to 16 columns + * depending on viewport, while **Bootstrap**-based themes have 12 columns. + */ +export type ColumnedFields> = Array<{ name?: string; label?: string; + + /** Size in proportion of the theme's grid configuration and full size */ size?: number; + /** Offset in proportion of the theme's grid configuration and full size */ offset?: number; - fields: Array; + fields: Array; }>; +/** */ export class Columns implements FieldInterface { public readonly formComponent: FormFieldTheme = 'column'; public readonly viewComponent: ViewFieldTheme = 'column'; diff --git a/src/lib/FieldDefinitions/CrudEntity.ts b/src/lib/FieldDefinitions/CrudEntity.ts index dbb01a34..196aabed 100644 --- a/src/lib/FieldDefinitions/CrudEntity.ts +++ b/src/lib/FieldDefinitions/CrudEntity.ts @@ -2,6 +2,7 @@ import { Field, type InputFieldOptions } from '$lib/FieldDefinitions/definition' import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; import type { KeyValueObject } from '$lib/genericTypes'; +/** */ export type CrudEntityOptions = InputFieldOptions & { crud_name: string; list_provider_operation?: { @@ -17,6 +18,7 @@ export type CrudEntityOptions = InputFieldOptions & { }; }; +/** */ export class CrudEntityField extends Field { readonly formComponent: FormFieldTheme = 'crud_entity'; readonly viewComponent: ViewFieldTheme = 'crud_entity'; diff --git a/src/lib/FieldDefinitions/Date.ts b/src/lib/FieldDefinitions/Date.ts index a3893884..153d5ba8 100644 --- a/src/lib/FieldDefinitions/Date.ts +++ b/src/lib/FieldDefinitions/Date.ts @@ -1,10 +1,12 @@ import { Field, type InputFieldOptions } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type DateOptions = InputFieldOptions & { formFormat?: string; // Default: 'Y-m-d' }; +/** */ export class DateField extends Field { readonly formComponent: FormFieldTheme = 'date'; readonly viewComponent: ViewFieldTheme = 'date'; diff --git a/src/lib/FieldDefinitions/KeyValueObject.ts b/src/lib/FieldDefinitions/KeyValueObject.ts index ea6f7047..4e797450 100644 --- a/src/lib/FieldDefinitions/KeyValueObject.ts +++ b/src/lib/FieldDefinitions/KeyValueObject.ts @@ -1,10 +1,12 @@ import { Field, type InputFieldOptions } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type ObjectOptions = InputFieldOptions & { // maxDepth?: number, // TODO: check if we can create an input with nested key=>value pairs with a max depth }; +/** */ export class KeyValueObjectField extends Field { readonly formComponent: FormFieldTheme = 'key_value_object'; readonly viewComponent: ViewFieldTheme = 'key_value_object'; diff --git a/src/lib/FieldDefinitions/Number.ts b/src/lib/FieldDefinitions/Number.ts index f934097b..bcba4d36 100644 --- a/src/lib/FieldDefinitions/Number.ts +++ b/src/lib/FieldDefinitions/Number.ts @@ -1,11 +1,13 @@ import { Field, type InputFieldOptions } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type NumberOptions = InputFieldOptions & { min?: number; max?: number; }; +/** */ export class NumberField extends Field { readonly formComponent: FormFieldTheme = 'number'; readonly viewComponent: ViewFieldTheme = 'number'; diff --git a/src/lib/FieldDefinitions/Tabs.ts b/src/lib/FieldDefinitions/Tabs.ts index 5a872839..4f4f4036 100644 --- a/src/lib/FieldDefinitions/Tabs.ts +++ b/src/lib/FieldDefinitions/Tabs.ts @@ -1,17 +1,20 @@ import type { FieldInterface, FieldOptions } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type TabOptions = FieldOptions & { name: string; label?: string; }; +/** */ export type TabbedFields> = Array<{ name: string; label?: string; fields: Array; }>; +/** */ export class Tabs implements FieldInterface { public readonly formComponent: FormFieldTheme = 'tabs'; public readonly viewComponent: ViewFieldTheme = 'tabs'; diff --git a/src/lib/FieldDefinitions/Text.ts b/src/lib/FieldDefinitions/Text.ts index aee1ee38..6e820bcf 100644 --- a/src/lib/FieldDefinitions/Text.ts +++ b/src/lib/FieldDefinitions/Text.ts @@ -1,11 +1,13 @@ import { Field, type InputFieldOptions } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type TextOptions = InputFieldOptions & { maxLength?: number; stripTags?: boolean; }; +/** */ export class TextField extends Field { readonly formComponent: FormFieldTheme = 'text'; readonly viewComponent: ViewFieldTheme = 'text'; diff --git a/src/lib/FieldDefinitions/Textarea.ts b/src/lib/FieldDefinitions/Textarea.ts index 6b463480..3d82fe67 100644 --- a/src/lib/FieldDefinitions/Textarea.ts +++ b/src/lib/FieldDefinitions/Textarea.ts @@ -2,10 +2,12 @@ import { Field } from '$lib/FieldDefinitions/definition'; import type { TextOptions } from '$lib/FieldDefinitions/Text'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type TextareaOptions = TextOptions & { rows?: number; }; +/** */ export class TextareaField extends Field { readonly formComponent: FormFieldTheme = 'textarea'; readonly viewComponent: ViewFieldTheme = 'textarea'; diff --git a/src/lib/FieldDefinitions/Toggle.ts b/src/lib/FieldDefinitions/Toggle.ts index 13e9023a..315db5ce 100644 --- a/src/lib/FieldDefinitions/Toggle.ts +++ b/src/lib/FieldDefinitions/Toggle.ts @@ -1,8 +1,10 @@ import { type CommonFieldOptions, Field } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type ToggleOptions = CommonFieldOptions; +/** */ export class ToggleField extends Field { readonly formComponent: FormFieldTheme = 'toggle'; readonly viewComponent: ViewFieldTheme = 'toggle'; diff --git a/src/lib/FieldDefinitions/Url.ts b/src/lib/FieldDefinitions/Url.ts index 9151c7d9..54a0e969 100644 --- a/src/lib/FieldDefinitions/Url.ts +++ b/src/lib/FieldDefinitions/Url.ts @@ -1,10 +1,12 @@ import { Field, type InputFieldOptions } from '$lib/FieldDefinitions/definition'; import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type UrlOptions = InputFieldOptions & { openInNewTab: boolean; }; +/** */ export class UrlField extends Field { readonly formComponent: FormFieldTheme = 'url'; readonly viewComponent: ViewFieldTheme = 'url'; diff --git a/src/lib/FieldDefinitions/definition.ts b/src/lib/FieldDefinitions/definition.ts index 8fa54df9..2c820251 100644 --- a/src/lib/FieldDefinitions/definition.ts +++ b/src/lib/FieldDefinitions/definition.ts @@ -1,10 +1,12 @@ import type { FormFieldTheme, ViewFieldTheme } from '$lib/themes/ThemeConfig'; +/** */ export type FieldOptions = { disableOnOperations?: Array; [key: string]: string | number | boolean | unknown; }; +/** */ export type CommonFieldOptions = FieldOptions & { required?: boolean; disabled?: boolean; @@ -12,26 +14,31 @@ export type CommonFieldOptions = FieldOptions & { help?: string; }; +/** */ export type InputFieldOptions = CommonFieldOptions & { placeholder?: string; }; -export interface FieldInterface { +/** */ +export interface FieldInterface { readonly name: string; readonly label: string; - readonly options: T; + readonly options: OptionsType; readonly formComponent: FormFieldTheme; readonly viewComponent: ViewFieldTheme; } -export class Field implements FieldInterface { +/** + * @abstract + **/ +export class Field implements FieldInterface { public readonly formComponent: FormFieldTheme = 'default'; public readonly viewComponent: ViewFieldTheme = 'default'; constructor( public readonly name: string, public readonly label: string = '', - public readonly options: T = {} as T + public readonly options: OptionsType = {} as OptionsType ) { this.label = label || name; } diff --git a/src/lib/Filter/index.ts b/src/lib/Filter/index.ts index dd1c49e0..5094d494 100644 --- a/src/lib/Filter/index.ts +++ b/src/lib/Filter/index.ts @@ -1,8 +1,10 @@ import type { KeyValueObject } from '$lib/genericTypes'; import type { FilterTheme } from '$lib/themes/ThemeConfig'; +/** */ export type FilterOptions = KeyValueObject; +/** */ export interface FilterInterface { readonly field: string; readonly label: string; @@ -10,6 +12,7 @@ export interface FilterInterface { readonly componentName: FilterTheme; } +/** */ export abstract class Filter implements FilterInterface { public readonly field: string; public readonly label: string; @@ -23,22 +26,27 @@ export abstract class Filter implements FilterInterface } } +/** */ export class TextFilter extends Filter { public readonly componentName: FilterTheme = 'text'; } +/** */ export class BooleanFilter extends Filter { public readonly componentName: FilterTheme = 'boolean'; } +/** */ export class DateFilter extends Filter { public readonly componentName: FilterTheme = 'date'; } +/** */ export class ExistsFilter extends Filter { public readonly componentName: FilterTheme = 'boolean'; } +/** */ export class NumericFilter extends Filter { public readonly componentName: FilterTheme = 'numeric'; } diff --git a/src/lib/Menu/MenuLinks.ts b/src/lib/Menu/MenuLinks.ts index 6a612a97..a3be6048 100644 --- a/src/lib/Menu/MenuLinks.ts +++ b/src/lib/Menu/MenuLinks.ts @@ -1,8 +1,10 @@ import {type Action, type ActionIcon, DefaultAction} from '$lib/actions'; import type { Optional } from '$lib/genericTypes'; +/** */ export type MenuLink = Action; +/** */ export class Submenu extends DefaultAction { private readonly _links: Array; @@ -16,6 +18,7 @@ export class Submenu extends DefaultAction { } } +/** */ export class Divider extends DefaultAction { constructor() { super('divider', undefined, {}); diff --git a/src/lib/Menu/stores.ts b/src/lib/Menu/stores.ts index 8b90925a..3584f2bd 100644 --- a/src/lib/Menu/stores.ts +++ b/src/lib/Menu/stores.ts @@ -1,3 +1,4 @@ import { writable } from 'svelte/store'; +/** @ignore */ export const sideMenuOpen = writable(false); diff --git a/src/lib/State/Processor.ts b/src/lib/State/Processor.ts index 670197d8..93263e27 100644 --- a/src/lib/State/Processor.ts +++ b/src/lib/State/Processor.ts @@ -1,8 +1,10 @@ import type { CrudOperation } from '$lib/Crud/Operations'; import type { RequestParameters } from '$lib/request'; +/** */ export type StateProcessorInput = T | Array | null; +/** */ export interface StateProcessor { process( data: StateProcessorInput, @@ -11,12 +13,14 @@ export interface StateProcessor { ): Promise; } +/** */ export type StateProcessorCallback = ( data: StateProcessorInput, operation: CrudOperation, requestParameters: RequestParameters ) => void; +/** */ export class CallbackStateProcessor implements StateProcessor { private readonly _callback: StateProcessorCallback; diff --git a/src/lib/State/Provider.ts b/src/lib/State/Provider.ts index 084f4ed1..5cefcab1 100644 --- a/src/lib/State/Provider.ts +++ b/src/lib/State/Provider.ts @@ -2,17 +2,21 @@ import type { CrudOperation } from '$lib/Crud/Operations'; import type { PaginatedResults } from '$lib/DataTable/Pagination'; import type { RequestParameters } from '$lib/request'; +/** */ export type StateProviderResult = Promise | Array | null>; +/** */ export interface StateProvider { provide(action: CrudOperation, requestParameters: RequestParameters): StateProviderResult; } +/** */ export type StateProviderCallback = ( action: CrudOperation, requestParameters: RequestParameters ) => StateProviderResult; +/** */ export class CallbackStateProvider implements StateProvider { private readonly _callback: StateProviderCallback; diff --git a/src/lib/actions.ts b/src/lib/actions.ts index 2c9d3e54..9ead7de5 100644 --- a/src/lib/actions.ts +++ b/src/lib/actions.ts @@ -1,17 +1,23 @@ import type { ComponentType, SvelteComponent } from 'svelte'; import type { KeyValueObject, Optional } from '$lib/genericTypes'; +/** */ export type ActionIcon = string | SvelteComponent | ComponentType; +/** */ export type ActionOptions = { buttonKind?: string; }; +/** + * @interface + **/ export interface Action { get label(): string; get icon(): ActionIcon | null | undefined; get options(): ActionOptions; } +/** */ export abstract class DefaultAction implements Action { protected readonly _label: string; protected readonly _icon?: Optional; @@ -36,6 +42,7 @@ export abstract class DefaultAction implements Action { } } +/** */ export class CallbackAction extends DefaultAction { private readonly _callback: (item?: object | undefined) => void; @@ -54,6 +61,7 @@ export class CallbackAction extends DefaultAction { } } +/** */ export class UrlAction extends DefaultAction { private readonly _url: string; diff --git a/src/lib/admin_i18n.ts b/src/lib/admin_i18n.ts index f52bd55b..db0bac86 100644 --- a/src/lib/admin_i18n.ts +++ b/src/lib/admin_i18n.ts @@ -5,7 +5,10 @@ import type { KeyValueObject } from '$lib/genericTypes'; import en from '$lib/translations/en'; import fr from '$lib/translations/fr'; +/** */ export type Dictionary = KeyValueObject; + +/** */ export type Dictionaries = { [key: string]: Dictionary }; const adminDictionaries: Dictionaries = { @@ -13,6 +16,7 @@ const adminDictionaries: Dictionaries = { fr: fr }; +/** */ export function initLocale(locale: Intl.Locale | string, dictionaries: Dictionaries = {}) { locale = locale.toString(); diff --git a/src/lib/config/adminConfig.ts b/src/lib/config/adminConfig.ts index cb9e5fe7..6552f5cb 100644 --- a/src/lib/config/adminConfig.ts +++ b/src/lib/config/adminConfig.ts @@ -1,6 +1,7 @@ import type { ThemeConfig } from '$lib/themes/ThemeConfig'; import carbon from '$lib/themes/carbon'; +/** */ export type AdminConfig = { theme: ThemeConfig; defaultLocale: string; diff --git a/src/lib/config/types.ts b/src/lib/config/types.ts index 99b7983e..23f4de1a 100644 --- a/src/lib/config/types.ts +++ b/src/lib/config/types.ts @@ -1,3 +1,4 @@ +/** */ export type SubmitButtonType = | 'primary' | 'secondary' diff --git a/src/lib/genericTypes.ts b/src/lib/genericTypes.ts index d93ebc22..06aaad8c 100644 --- a/src/lib/genericTypes.ts +++ b/src/lib/genericTypes.ts @@ -1,3 +1,5 @@ +/** */ export type Optional = T | null | undefined; +/** */ export type KeyValueObject = { [key: string]: string }; diff --git a/src/lib/messages/messages.ts b/src/lib/messages/messages.ts index f4146a6a..69ba837e 100644 --- a/src/lib/messages/messages.ts +++ b/src/lib/messages/messages.ts @@ -1,5 +1,6 @@ import { toast } from '@zerodevx/svelte-toast'; +/** */ export default function message(content: string, type: ToastType) { const toast = new Toast(content, type || 'info'); @@ -8,24 +9,30 @@ export default function message(content: string, type: ToastType) { return toast; } +/** */ export function success(content: string): Toast { return message(content, 'success'); } +/** */ export function error(content: string): Toast { return message(content, 'error'); } +/** */ export function warning(content: string): Toast { return message(content, 'warning'); } +/** */ export function info(content: string): Toast { return message(content, 'info'); } +/** */ export type ToastType = 'info' | 'success' | 'warning' | 'error'; +/** */ export class Toast { private readonly _content: string; private readonly _toast_type: ToastType; diff --git a/src/lib/request.ts b/src/lib/request.ts index 3bc7884a..fd7efb02 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -1,6 +1,7 @@ import type { Page } from '@sveltejs/kit'; import type { Optional } from '$lib/genericTypes'; +/** */ export type RequestParameters = { page?: Optional; filters?: Optional>; diff --git a/src/lib/themes/ThemeConfig.ts b/src/lib/themes/ThemeConfig.ts index 0c13308e..693ad2a4 100644 --- a/src/lib/themes/ThemeConfig.ts +++ b/src/lib/themes/ThemeConfig.ts @@ -1,5 +1,6 @@ import type { ComponentType } from 'svelte'; +/** */ export type ThemeConfig = { dashboard: ComponentType; dataTable: ComponentType; @@ -61,8 +62,8 @@ export type ThemeConfig = { }; }; -export type CrudTheme = keyof ThemeConfig['crudActions']; -export type ViewFieldTheme = keyof ThemeConfig['viewFields']; -export type FormFieldTheme = keyof ThemeConfig['formFields']; -export type FilterTheme = keyof ThemeConfig['filters']; -export type MenuTheme = keyof ThemeConfig['menu']; +/** */ export type CrudTheme = keyof ThemeConfig['crudActions']; +/** */ export type ViewFieldTheme = keyof ThemeConfig['viewFields']; +/** */ export type FormFieldTheme = keyof ThemeConfig['formFields']; +/** */ export type FilterTheme = keyof ThemeConfig['filters']; +/** */ export type MenuTheme = keyof ThemeConfig['menu']; diff --git a/src/testApp/Dashboard.ts b/src/testApp/Dashboard.ts index 8353a4b4..1e59cd74 100644 --- a/src/testApp/Dashboard.ts +++ b/src/testApp/Dashboard.ts @@ -1,5 +1,6 @@ import Book from 'carbon-icons-svelte/lib/Book.svelte'; import Home from 'carbon-icons-svelte/lib/Home.svelte'; +import Document from 'carbon-icons-svelte/lib/Document.svelte'; import { DashboardDefinition, CallbackAction, UrlAction, Submenu, themes } from '$lib'; @@ -29,6 +30,9 @@ export const dashboard = new DashboardDefinition({ new UrlAction('Submenu 2', '#', Book) ]) ], + topLeftMenu: [ + new UrlAction('Docs', '/docs', Document), + ], localeDictionaries: { fr: fr }, diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 00000000..165bb80d --- /dev/null +++ b/typedoc.json @@ -0,0 +1,14 @@ +{ + "entryPoints": ["src/lib/*.ts", "src/lib/**/*.ts"], + "out": "build/docs", + "exclude": ["**/*+(index|.spec|.e2e).ts"], + "disableGit": true, + "disableSources": true, + "excludeExternals": false, + "excludeNotDocumented": true, + "excludePrivate": true, + "excludeReferences": true, + "plugin": [ + "typedoc-plugin-mdn-links" + ] +} diff --git a/yarn.lock b/yarn.lock index e7797215..85e03a03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -483,6 +483,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/node@^20.11.17": + version "20.11.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.17.tgz#cdd642d0e62ef3a861f88ddbc2b61e32578a9292" + integrity sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw== + dependencies: + undici-types "~5.26.4" + "@types/pug@^2.0.6": version "2.0.10" resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.10.tgz#52f8dbd6113517aef901db20b4f3fca543b88c1f" @@ -687,6 +694,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-sequence-parser@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf" + integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== + ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -1695,6 +1707,11 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +lunr@^2.3.9: + version "2.3.9" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + luxon@^3.4.4: version "3.4.4" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" @@ -1723,6 +1740,11 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" +marked@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== + mdn-data@2.0.30: version "2.0.30" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" @@ -1782,7 +1804,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@9.0.3: +minimatch@9.0.3, minimatch@^9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== @@ -2220,6 +2242,16 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shiki@^0.14.7: + version "0.14.7" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.7.tgz#c3c9e1853e9737845f1d2ef81b31bcfb07056d4e" + integrity sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg== + dependencies: + ansi-sequence-parser "^1.1.0" + jsonc-parser "^3.2.0" + vscode-oniguruma "^1.7.0" + vscode-textmate "^8.0.0" + siginfo@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" @@ -2492,6 +2524,21 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== +typedoc-plugin-mdn-links@^3.1.16: + version "3.1.16" + resolved "https://registry.yarnpkg.com/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-3.1.16.tgz#936408cff409b8f9b2b55d9f6589850c488a9858" + integrity sha512-Jdnw3tI3KOOMMahUNiCwKXJcH+Ov9y3syRQ8HP1ce8nT2O9Bsg9aqqa0LWye+CdCiSpWJ/USzOK3MFNxRYzL4A== + +typedoc@^0.25.8: + version "0.25.8" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.25.8.tgz#7d0e1bf12d23bf1c459fd4893c82cb855911ff12" + integrity sha512-mh8oLW66nwmeB9uTa0Bdcjfis+48bAjSH3uqdzSuSawfduROQLlXw//WSNZLYDdhmMVB7YcYZicq6e8T0d271A== + dependencies: + lunr "^2.3.9" + marked "^4.3.0" + minimatch "^9.0.3" + shiki "^0.14.7" + typescript@^5.0.3, typescript@^5.3.3: version "5.3.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" @@ -2502,6 +2549,11 @@ ufo@^1.3.0: resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496" integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -2577,6 +2629,16 @@ vitest@^1.0.4: vite-node "1.1.3" why-is-node-running "^2.2.2" +vscode-oniguruma@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== + +vscode-textmate@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" + integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"