From 2ee1a616898a5032e34f51c4a6dfde1c967d79f3 Mon Sep 17 00:00:00 2001 From: lelemm Date: Wed, 24 Sep 2025 08:52:46 -0300 Subject: [PATCH] Plugins-core --- .github/workflows/build.yml | 16 + eslint.config.mjs | 6 + package.json | 1 + packages/api/tsconfig.dist.json | 2 +- packages/component-library/package.json | 4 +- packages/component-library/src/index.ts | 34 ++ .../component-library/src/props/modalProps.ts | 11 + .../src/spreadsheet/bindings.ts | 8 +- .../desktop-client/src/spreadsheet/index.ts | 127 +--- packages/loot-core/src/shared/query.ts | 183 +----- .../loot-core/src/types/models/account.ts | 30 +- .../src/types/models/category-group.ts | 12 +- .../loot-core/src/types/models/category.ts | 14 +- packages/loot-core/src/types/models/payee.ts | 11 +- packages/loot-core/src/types/models/rule.ts | 200 +------ .../loot-core/src/types/models/schedule.ts | 55 +- packages/loot-core/src/types/util.ts | 2 +- packages/loot-core/tsconfig.api.json | 1 - packages/loot-core/vite.api.config.ts | 8 + packages/loot-core/vite.config.ts | 8 + packages/loot-core/vite.desktop.config.ts | 8 + packages/plugins-core/package.json | 59 ++ .../plugins-core/src/BasicModalComponents.tsx | 228 ++++++++ packages/plugins-core/src/client.ts | 37 ++ packages/plugins-core/src/index.ts | 13 + packages/plugins-core/src/middleware.ts | 105 ++++ packages/plugins-core/src/server.ts | 62 ++ .../plugins-core/src/types/actualPlugin.ts | 521 +++++++++++++++++ .../src/types/actualPluginEntry.ts | 5 + .../src/types/actualPluginManifest.ts | 11 + .../plugins-core/src/types/plugin-files.ts | 18 + packages/plugins-core/src/types/toolkit.ts | 7 + packages/plugins-core/src/utils.ts | 24 + packages/plugins-core/tsconfig.json | 25 + packages/plugins-core/vite.config.mts | 55 ++ packages/query/package.json | 36 ++ packages/query/src/aql-result.ts | 27 + packages/query/src/database.ts | 51 ++ packages/query/src/index.ts | 179 ++++++ packages/query/tsconfig.json | 17 + packages/shared-types/package.json | 29 + packages/shared-types/src/index.ts | 8 + packages/shared-types/src/models/account.ts | 25 + .../shared-types/src/models/category-group.ts | 11 + .../shared-types/src/models/category-views.ts | 7 + packages/shared-types/src/models/category.ts | 13 + packages/shared-types/src/models/index.ts | 7 + packages/shared-types/src/models/payee.ts | 10 + packages/shared-types/src/models/rule.ts | 186 ++++++ packages/shared-types/src/models/schedule.ts | 49 ++ packages/shared-types/src/query.ts | 54 ++ packages/shared-types/src/spreadsheet.ts | 127 ++++ packages/shared-types/src/util.ts | 5 + packages/shared-types/tsconfig.json | 9 + tsconfig.json | 21 +- upcoming-release-notes/5786.md | 7 + yarn.lock | 543 +++++++++++++++++- 57 files changed, 2714 insertions(+), 618 deletions(-) create mode 100644 packages/component-library/src/index.ts create mode 100644 packages/component-library/src/props/modalProps.ts create mode 100644 packages/plugins-core/package.json create mode 100644 packages/plugins-core/src/BasicModalComponents.tsx create mode 100644 packages/plugins-core/src/client.ts create mode 100644 packages/plugins-core/src/index.ts create mode 100644 packages/plugins-core/src/middleware.ts create mode 100644 packages/plugins-core/src/server.ts create mode 100644 packages/plugins-core/src/types/actualPlugin.ts create mode 100644 packages/plugins-core/src/types/actualPluginEntry.ts create mode 100644 packages/plugins-core/src/types/actualPluginManifest.ts create mode 100644 packages/plugins-core/src/types/plugin-files.ts create mode 100644 packages/plugins-core/src/types/toolkit.ts create mode 100644 packages/plugins-core/src/utils.ts create mode 100644 packages/plugins-core/tsconfig.json create mode 100644 packages/plugins-core/vite.config.mts create mode 100644 packages/query/package.json create mode 100644 packages/query/src/aql-result.ts create mode 100644 packages/query/src/database.ts create mode 100644 packages/query/src/index.ts create mode 100644 packages/query/tsconfig.json create mode 100644 packages/shared-types/package.json create mode 100644 packages/shared-types/src/index.ts create mode 100644 packages/shared-types/src/models/account.ts create mode 100644 packages/shared-types/src/models/category-group.ts create mode 100644 packages/shared-types/src/models/category-views.ts create mode 100644 packages/shared-types/src/models/category.ts create mode 100644 packages/shared-types/src/models/index.ts create mode 100644 packages/shared-types/src/models/payee.ts create mode 100644 packages/shared-types/src/models/rule.ts create mode 100644 packages/shared-types/src/models/schedule.ts create mode 100644 packages/shared-types/src/query.ts create mode 100644 packages/shared-types/src/spreadsheet.ts create mode 100644 packages/shared-types/src/util.ts create mode 100644 packages/shared-types/tsconfig.json create mode 100644 upcoming-release-notes/5786.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fb9db222d..a744576ed8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,22 @@ jobs: name: actual-crdt path: packages/crdt/actual-crdt.tgz + plugins-core: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - name: Build Plugins Core + run: yarn workspace @actual-app/plugins-core build + - name: Create package tgz + run: yarn workspace @actual-app/plugins-core pack --filename actual-plugins-core.tgz + - name: Upload Build + uses: actions/upload-artifact@v4 + with: + name: actual-plugins-core + path: packages/plugins-core/actual-plugins-core.tgz + web: runs-on: ubuntu-latest steps: diff --git a/eslint.config.mjs b/eslint.config.mjs index 34e5968bc4..e2fcb831cc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -101,8 +101,14 @@ export default defineConfig( 'packages/loot-core/**/proto/*', 'packages/sync-server/build/', 'packages/plugins-service/dist/', + 'packages/plugins-core/build/', + 'packages/plugins-core/node_modules/', '.yarn/*', '.github/*', + 'packages/shared-types/dist/', + 'packages/shared-types/build/', + 'packages/query/dist/', + 'packages/query/build/', ], }, { diff --git a/package.json b/package.json index 33352624ea..0065c66244 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "build:desktop": "./bin/package-electron", "build:plugins-service": "yarn workspace plugins-service build", "build:api": "yarn workspace @actual-app/api build", + "build:plugins-core": "yarn workspace @actual-app/plugins-core build", "generate:i18n": "yarn workspace @actual-app/web generate:i18n", "generate:release-notes": "ts-node ./bin/release-note-generator.ts", "test": "lage test --continue", diff --git a/packages/api/tsconfig.dist.json b/packages/api/tsconfig.dist.json index adc9dcd0b7..40dbfefa8d 100644 --- a/packages/api/tsconfig.dist.json +++ b/packages/api/tsconfig.dist.json @@ -11,7 +11,7 @@ "outDir": "dist", "declarationDir": "@types", "paths": { - "loot-core/*": ["./@types/loot-core/src/*"] + "loot-core/*": ["./@types/loot-core/loot-core/src/*"] } }, "include": ["."], diff --git a/packages/component-library/package.json b/packages/component-library/package.json index c724a2eebd..97aa18240f 100644 --- a/packages/component-library/package.json +++ b/packages/component-library/package.json @@ -19,6 +19,7 @@ "vitest": "^3.2.4" }, "exports": { + ".": "./src/index.ts", "./hooks/*": "./src/hooks/*.ts", "./icons/logo": "./src/icons/logo/index.ts", "./icons/v0": "./src/icons/v0/index.ts", @@ -49,7 +50,8 @@ "./toggle": "./src/Toggle.tsx", "./tooltip": "./src/Tooltip.tsx", "./view": "./src/View.tsx", - "./color-picker": "./src/ColorPicker.tsx" + "./color-picker": "./src/ColorPicker.tsx", + "./props/*": "./src/props/*.ts" }, "scripts": { "generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .", diff --git a/packages/component-library/src/index.ts b/packages/component-library/src/index.ts new file mode 100644 index 0000000000..a6e7153437 --- /dev/null +++ b/packages/component-library/src/index.ts @@ -0,0 +1,34 @@ +// Components +export { AlignedText } from './AlignedText'; +export { Block } from './Block'; +export { Button, ButtonWithLoading } from './Button'; +export { Card } from './Card'; +export { ColorPicker } from './ColorPicker'; +export { FormError } from './FormError'; +export { InitialFocus } from './InitialFocus'; +export { InlineField } from './InlineField'; +export { Input } from './Input'; +export { Label } from './Label'; +export { Menu } from './Menu'; +export { Paragraph } from './Paragraph'; +export { Popover } from './Popover'; +export { Select } from './Select'; +export { SpaceBetween } from './SpaceBetween'; +export { Stack } from './Stack'; +export { Text } from './Text'; +export { TextOneLine } from './TextOneLine'; +export { Toggle } from './Toggle'; +export { Tooltip } from './Tooltip'; +export { View } from './View'; + +export * from './icons/v2'; +export * from './icons/logo'; +export { AnimatedLoading } from './icons/AnimatedLoading'; +export { SvgLoading as Loading } from './icons/Loading'; + +// Styles, Theme, Tokens +export * from './styles'; +export * from './theme'; +export * from './tokens'; + +export type * from './props/modalProps'; diff --git a/packages/component-library/src/props/modalProps.ts b/packages/component-library/src/props/modalProps.ts new file mode 100644 index 0000000000..22d8ba2615 --- /dev/null +++ b/packages/component-library/src/props/modalProps.ts @@ -0,0 +1,11 @@ +import { type CSSProperties } from 'react'; + +export type BasicModalProps = { + isLoading?: boolean; + noAnimation?: boolean; + style?: CSSProperties; + onClose?: () => void; + containerProps?: { + style?: CSSProperties; + }; +}; diff --git a/packages/desktop-client/src/spreadsheet/bindings.ts b/packages/desktop-client/src/spreadsheet/bindings.ts index c4f2683175..21d1607012 100644 --- a/packages/desktop-client/src/spreadsheet/bindings.ts +++ b/packages/desktop-client/src/spreadsheet/bindings.ts @@ -1,12 +1,12 @@ -import { q } from 'loot-core/shared/query'; -import type { AccountEntity, CategoryEntity } from 'loot-core/types/models'; - import { parametrizedField, type SheetFields, type Binding, type SheetNames, -} from '.'; +} from '@actual-app/plugins-core'; + +import { q } from 'loot-core/shared/query'; +import type { AccountEntity, CategoryEntity } from 'loot-core/types/models'; import { uncategorizedTransactions } from '@desktop-client/queries'; diff --git a/packages/desktop-client/src/spreadsheet/index.ts b/packages/desktop-client/src/spreadsheet/index.ts index c3fc584ef6..c799db5f13 100644 --- a/packages/desktop-client/src/spreadsheet/index.ts +++ b/packages/desktop-client/src/spreadsheet/index.ts @@ -1,119 +1,8 @@ -import { type Query } from 'loot-core/shared/query'; - -export type Spreadsheets = { - account: { - // Common fields - 'uncategorized-amount': number; - 'uncategorized-balance': number; - - // Account fields - balance: number; - [key: `balance-${string}-cleared`]: number | null; - 'accounts-balance': number; - 'onbudget-accounts-balance': number; - 'offbudget-accounts-balance': number; - 'closed-accounts-balance': number; - balanceCleared: number; - balanceUncleared: number; - lastReconciled: string | null; - }; - category: { - // Common fields - 'uncategorized-amount': number; - 'uncategorized-balance': number; - - balance: number; - balanceCleared: number; - balanceUncleared: number; - }; - 'envelope-budget': { - // Common fields - 'uncategorized-amount': number; - 'uncategorized-balance': number; - - // Envelope budget fields - 'available-funds': number; - 'last-month-overspent': number; - buffered: number; - 'buffered-auto': number; - 'buffered-selected': number; - 'to-budget': number | null; - 'from-last-month': number; - 'total-budgeted': number; - 'total-income': number; - 'total-spent': number; - 'total-leftover': number; - 'group-sum-amount': number; - 'group-budget': number; - 'group-leftover': number; - budget: number; - 'sum-amount': number; - leftover: number; - carryover: number; - goal: number; - 'long-goal': number; - }; - 'tracking-budget': { - // Common fields - 'uncategorized-amount': number; - 'uncategorized-balance': number; - - // Tracking budget fields - 'total-budgeted': number; - 'total-budget-income': number; - 'total-saved': number; - 'total-income': number; - 'total-spent': number; - 'real-saved': number; - 'total-leftover': number; - 'group-sum-amount': number; - 'group-budget': number; - 'group-leftover': number; - budget: number; - 'sum-amount': number; - leftover: number; - carryover: number; - goal: number; - 'long-goal': number; - }; - [`balance`]: { - // Common fields - 'uncategorized-amount': number; - 'uncategorized-balance': number; - - // Balance fields - [key: `balance-query-${string}`]: number; - [key: `selected-transactions-${string}`]: Array<{ id: string }>; - [key: `selected-balance-${string}`]: number; - }; -}; - -export type SheetNames = keyof Spreadsheets & string; - -export type SheetFields = - keyof Spreadsheets[SheetName] & string; - -export type BindingObject< - SheetName extends SheetNames, - SheetFieldName extends SheetFields, -> = { - name: SheetFieldName; - value?: Spreadsheets[SheetName][SheetFieldName] | undefined; - query?: Query | undefined; -}; - -export type Binding< - SheetName extends SheetNames, - SheetFieldName extends SheetFields, -> = - | SheetFieldName - | { - name: SheetFieldName; - value?: Spreadsheets[SheetName][SheetFieldName] | undefined; - query?: Query | undefined; - }; -export const parametrizedField = - () => - >(field: SheetFieldName) => - (id?: string): SheetFieldName => - `${field}-${id}` as SheetFieldName; +export type { + Spreadsheets, + SheetNames, + SheetFields, + BindingObject, + Binding, +} from '@actual-app/shared-types/spreadsheet'; +export { parametrizedField } from '@actual-app/shared-types/spreadsheet'; diff --git a/packages/loot-core/src/shared/query.ts b/packages/loot-core/src/shared/query.ts index b53ad14986..b290ce3616 100644 --- a/packages/loot-core/src/shared/query.ts +++ b/packages/loot-core/src/shared/query.ts @@ -1,176 +1,7 @@ -import { WithRequired } from '../types/util'; - -type ObjectExpression = { - [key: string]: ObjectExpression | unknown; -}; - -export type QueryState = { - get table(): string; - get tableOptions(): Readonly>; - get filterExpressions(): ReadonlyArray; - get selectExpressions(): ReadonlyArray; - get groupExpressions(): ReadonlyArray; - get orderExpressions(): ReadonlyArray; - get calculation(): boolean; - get rawMode(): boolean; - get withDead(): boolean; - get validateRefs(): boolean; - get limit(): number | null; - get offset(): number | null; -}; - -export class Query { - state: QueryState; - - constructor(state: WithRequired, 'table'>) { - this.state = { - tableOptions: state.tableOptions || {}, - filterExpressions: state.filterExpressions || [], - selectExpressions: state.selectExpressions || [], - groupExpressions: state.groupExpressions || [], - orderExpressions: state.orderExpressions || [], - calculation: false, - rawMode: false, - withDead: false, - validateRefs: true, - limit: null, - offset: null, - ...state, - }; - } - - filter(expr: ObjectExpression) { - return new Query({ - ...this.state, - filterExpressions: [...this.state.filterExpressions, expr], - }); - } - - unfilter(exprs?: Array) { - // Remove all filters if no arguments are passed - if (!exprs) { - return new Query({ - ...this.state, - filterExpressions: [], - }); - } - - const exprSet = new Set(exprs); - return new Query({ - ...this.state, - filterExpressions: this.state.filterExpressions.filter( - expr => !exprSet.has(Object.keys(expr)[0]), - ), - }); - } - - select( - exprs: - | Array - | ObjectExpression - | string - | '*' - | ['*'] = [], - ) { - if (!Array.isArray(exprs)) { - exprs = [exprs]; - } - - return new Query({ - ...this.state, - selectExpressions: exprs, - calculation: false, - }); - } - - calculate(expr: ObjectExpression | string) { - return new Query({ - ...this.state, - selectExpressions: [{ result: expr }], - calculation: true, - }); - } - - groupBy(exprs: ObjectExpression | string | Array) { - if (!Array.isArray(exprs)) { - exprs = [exprs]; - } - - return new Query({ - ...this.state, - groupExpressions: [...this.state.groupExpressions, ...exprs], - }); - } - - orderBy(exprs: ObjectExpression | string | Array) { - if (!Array.isArray(exprs)) { - exprs = [exprs]; - } - - return new Query({ - ...this.state, - orderExpressions: [...this.state.orderExpressions, ...exprs], - }); - } - - limit(num: number) { - return new Query({ ...this.state, limit: num }); - } - - offset(num: number) { - return new Query({ ...this.state, offset: num }); - } - - raw() { - return new Query({ ...this.state, rawMode: true }); - } - - withDead() { - return new Query({ ...this.state, withDead: true }); - } - - withoutValidatedRefs() { - return new Query({ ...this.state, validateRefs: false }); - } - - options(opts: Record) { - return new Query({ ...this.state, tableOptions: opts }); - } - - reset() { - return q(this.state.table); - } - - serialize() { - return this.state; - } - - serializeAsString() { - return JSON.stringify(this.serialize()); - } -} - -export function getPrimaryOrderBy( - query: Query, - defaultOrderBy: ObjectExpression | null, -) { - const orderExprs = query.serialize().orderExpressions; - if (orderExprs.length === 0) { - if (defaultOrderBy) { - return { order: 'asc', ...defaultOrderBy }; - } - return null; - } - - const firstOrder = orderExprs[0]; - if (typeof firstOrder === 'string') { - return { field: firstOrder, order: 'asc' }; - } - // Handle this form: { field: 'desc' } - const [field] = Object.keys(firstOrder); - return { field, order: firstOrder[field] }; -} - -export function q(table: QueryState['table']) { - return new Query({ table }); -} +// Re-export query types and functions from @actual-app/query +export type { + ObjectExpression, + QueryState, + QueryBuilder, +} from '@actual-app/query'; +export { Query, q, getPrimaryOrderBy } from '@actual-app/query'; diff --git a/packages/loot-core/src/types/models/account.ts b/packages/loot-core/src/types/models/account.ts index b10817fc72..5226ecae49 100644 --- a/packages/loot-core/src/types/models/account.ts +++ b/packages/loot-core/src/types/models/account.ts @@ -1,25 +1,5 @@ -export type AccountEntity = { - id: string; - name: string; - offbudget: 0 | 1; - closed: 0 | 1; - sort_order: number; - last_reconciled: string | null; - tombstone: 0 | 1; -} & (_SyncFields | _SyncFields); - -export type _SyncFields = { - account_id: T extends true ? string : null; - bank: T extends true ? string : null; - bankName: T extends true ? string : null; - bankId: T extends true ? number : null; - mask: T extends true ? string : null; // end of bank account number - official_name: T extends true ? string : null; - balance_current: T extends true ? number : null; - balance_available: T extends true ? number : null; - balance_limit: T extends true ? number : null; - account_sync_source: T extends true ? AccountSyncSource : null; - last_sync: T extends true ? string : null; -}; - -export type AccountSyncSource = 'simpleFin' | 'goCardless' | 'pluggyai'; +export type { + AccountEntity, + _SyncFields, + AccountSyncSource, +} from '@actual-app/shared-types/models/account'; diff --git a/packages/loot-core/src/types/models/category-group.ts b/packages/loot-core/src/types/models/category-group.ts index d128cf3070..c879839a11 100644 --- a/packages/loot-core/src/types/models/category-group.ts +++ b/packages/loot-core/src/types/models/category-group.ts @@ -1,11 +1 @@ -import { CategoryEntity } from './category'; - -export interface CategoryGroupEntity { - id: string; - name: string; - is_income?: boolean; - sort_order?: number; - tombstone?: boolean; - hidden?: boolean; - categories?: CategoryEntity[]; -} +export type { CategoryGroupEntity } from '@actual-app/shared-types/models/category-group'; diff --git a/packages/loot-core/src/types/models/category.ts b/packages/loot-core/src/types/models/category.ts index 5193181401..029e0576c3 100644 --- a/packages/loot-core/src/types/models/category.ts +++ b/packages/loot-core/src/types/models/category.ts @@ -1,13 +1 @@ -import { CategoryGroupEntity } from './category-group'; - -export interface CategoryEntity { - id: string; - name: string; - is_income?: boolean; - group: CategoryGroupEntity['id']; - goal_def?: string; - template_settings?: { source: 'notes' | 'ui' }; - sort_order?: number; - tombstone?: boolean; - hidden?: boolean; -} +export type { CategoryEntity } from '@actual-app/shared-types/models/category'; diff --git a/packages/loot-core/src/types/models/payee.ts b/packages/loot-core/src/types/models/payee.ts index a559c4b93f..faffa0e356 100644 --- a/packages/loot-core/src/types/models/payee.ts +++ b/packages/loot-core/src/types/models/payee.ts @@ -1,10 +1 @@ -import { AccountEntity } from './account'; - -export interface PayeeEntity { - id: string; - name: string; - transfer_acct?: AccountEntity['id']; - favorite?: boolean; - learn_categories?: boolean; - tombstone?: boolean; -} +export type { PayeeEntity } from '@actual-app/shared-types/models/payee'; diff --git a/packages/loot-core/src/types/models/rule.ts b/packages/loot-core/src/types/models/rule.ts index e59f9eee99..85d61ac178 100644 --- a/packages/loot-core/src/types/models/rule.ts +++ b/packages/loot-core/src/types/models/rule.ts @@ -1,186 +1,14 @@ -import { type RecurConfig, type ScheduleEntity } from './schedule'; - -export interface NewRuleEntity { - stage: 'pre' | null | 'post'; - conditionsOp: 'or' | 'and'; - conditions: RuleConditionEntity[]; - actions: RuleActionEntity[]; - tombstone?: boolean; -} - -export interface RuleEntity extends NewRuleEntity { - id: string; -} - -export type RuleConditionOp = - | 'is' - | 'isNot' - | 'oneOf' - | 'notOneOf' - | 'isapprox' - | 'isbetween' - | 'gt' - | 'gte' - | 'lt' - | 'lte' - | 'contains' - | 'doesNotContain' - | 'hasTags' - | 'and' - | 'matches' - | 'onBudget' - | 'offBudget'; - -export type FieldValueTypes = { - account: string; - amount: number; - category: string; - date: string | RecurConfig; - notes: string; - payee: string; - payee_name: string; - imported_payee: string; - saved: string; - transfer: boolean; - parent: boolean; - cleared: boolean; - reconciled: boolean; -}; - -type BaseConditionEntity< - Field extends keyof FieldValueTypes, - Op extends RuleConditionOp, -> = { - field: Field; - op: Op; - value: Op extends 'oneOf' | 'notOneOf' - ? Array - : Op extends 'isbetween' - ? { num1: number; num2: number } - : FieldValueTypes[Field]; - options?: { - inflow?: boolean; - outflow?: boolean; - month?: boolean; - year?: boolean; - }; - conditionsOp?: string; - type?: 'id' | 'boolean' | 'date' | 'number' | 'string'; - customName?: string; - queryFilter?: Record; -}; - -export type RuleConditionEntity = - | BaseConditionEntity< - 'account', - | 'is' - | 'isNot' - | 'oneOf' - | 'notOneOf' - | 'contains' - | 'doesNotContain' - | 'matches' - | 'onBudget' - | 'offBudget' - > - | BaseConditionEntity< - 'category', - | 'is' - | 'isNot' - | 'oneOf' - | 'notOneOf' - | 'contains' - | 'doesNotContain' - | 'matches' - > - | BaseConditionEntity< - 'amount', - 'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte' - > - | BaseConditionEntity< - 'date', - 'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte' - > - | BaseConditionEntity< - 'notes', - | 'is' - | 'isNot' - | 'oneOf' - | 'notOneOf' - | 'contains' - | 'doesNotContain' - | 'matches' - | 'hasTags' - > - | BaseConditionEntity< - 'payee', - | 'is' - | 'isNot' - | 'oneOf' - | 'notOneOf' - | 'contains' - | 'doesNotContain' - | 'matches' - > - | BaseConditionEntity< - 'imported_payee', - | 'is' - | 'isNot' - | 'oneOf' - | 'notOneOf' - | 'contains' - | 'doesNotContain' - | 'matches' - > - | BaseConditionEntity<'saved', 'is'> - | BaseConditionEntity<'cleared', 'is'> - | BaseConditionEntity<'reconciled', 'is'>; - -export type RuleActionEntity = - | SetRuleActionEntity - | SetSplitAmountRuleActionEntity - | LinkScheduleRuleActionEntity - | PrependNoteRuleActionEntity - | AppendNoteRuleActionEntity - | DeleteTransactionRuleActionEntity; - -export interface SetRuleActionEntity { - field: string; - op: 'set'; - value: unknown; - options?: { - template?: string; - formula?: string; - splitIndex?: number; - }; - type?: string; -} - -export interface SetSplitAmountRuleActionEntity { - op: 'set-split-amount'; - value: number; - options?: { - splitIndex?: number; - method: 'fixed-amount' | 'fixed-percent' | 'remainder'; - }; -} - -export interface LinkScheduleRuleActionEntity { - op: 'link-schedule'; - value: ScheduleEntity; -} - -export interface PrependNoteRuleActionEntity { - op: 'prepend-notes'; - value: string; -} - -export interface AppendNoteRuleActionEntity { - op: 'append-notes'; - value: string; -} - -export interface DeleteTransactionRuleActionEntity { - op: 'delete-transaction'; - value: string; -} +export type { + NewRuleEntity, + RuleEntity, + RuleConditionOp, + FieldValueTypes, + RuleConditionEntity, + RuleActionEntity, + SetRuleActionEntity, + SetSplitAmountRuleActionEntity, + LinkScheduleRuleActionEntity, + PrependNoteRuleActionEntity, + AppendNoteRuleActionEntity, + DeleteTransactionRuleActionEntity, +} from '@actual-app/shared-types/models/rule'; diff --git a/packages/loot-core/src/types/models/schedule.ts b/packages/loot-core/src/types/models/schedule.ts index 16ff20442e..23f991e762 100644 --- a/packages/loot-core/src/types/models/schedule.ts +++ b/packages/loot-core/src/types/models/schedule.ts @@ -1,49 +1,6 @@ -import type { AccountEntity } from './account'; -import type { PayeeEntity } from './payee'; -import type { RuleConditionEntity, RuleEntity } from './rule'; - -export interface RecurPattern { - value: number; - type: 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'day'; -} - -export interface RecurConfig { - frequency: 'daily' | 'weekly' | 'monthly' | 'yearly'; - interval?: number; - patterns?: RecurPattern[]; - skipWeekend?: boolean; - start: string; - endMode: 'never' | 'after_n_occurrences' | 'on_date'; - endOccurrences?: number; - endDate?: string; - weekendSolveMode?: 'before' | 'after'; -} - -export interface ScheduleEntity { - id: string; - name?: string; - rule: RuleEntity['id']; - next_date: string; - completed: boolean; - posts_transaction: boolean; - tombstone: boolean; - - // These are special fields that are actually pulled from the - // underlying rule - _payee: PayeeEntity['id']; - _account: AccountEntity['id']; - _amount: number | { num1: number; num2: number }; - _amountOp: string; - _date: RecurConfig; - _conditions: RuleConditionEntity[]; - _actions: Array<{ op: unknown }>; -} - -export type DiscoverScheduleEntity = { - id: ScheduleEntity['id']; - account: AccountEntity['id']; - payee: PayeeEntity['id']; - date: ScheduleEntity['_date']; - amount: ScheduleEntity['_amount']; - _conditions: ScheduleEntity['_conditions']; -}; +export type { + RecurPattern, + RecurConfig, + ScheduleEntity, + DiscoverScheduleEntity, +} from '@actual-app/shared-types/models/schedule'; diff --git a/packages/loot-core/src/types/util.ts b/packages/loot-core/src/types/util.ts index b7b05a8697..637688eed2 100644 --- a/packages/loot-core/src/types/util.ts +++ b/packages/loot-core/src/types/util.ts @@ -8,7 +8,7 @@ export type EverythingButIdOptional = { id: T['id']; } & Partial>; -export type WithRequired = T & Required>; +export type { WithRequired } from '@actual-app/shared-types'; // Allows use of object literals inside child elements of `Trans` tags // see https://github.com/i18next/react-i18next/issues/1483 diff --git a/packages/loot-core/tsconfig.api.json b/packages/loot-core/tsconfig.api.json index 56ea2d8cdc..85604a493a 100644 --- a/packages/loot-core/tsconfig.api.json +++ b/packages/loot-core/tsconfig.api.json @@ -1,7 +1,6 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": ".", "declaration": true, "emitDeclarationOnly": true, "allowJs": false, diff --git a/packages/loot-core/vite.api.config.ts b/packages/loot-core/vite.api.config.ts index 36f960175e..08e229726d 100644 --- a/packages/loot-core/vite.api.config.ts +++ b/packages/loot-core/vite.api.config.ts @@ -52,6 +52,14 @@ export default defineConfig(({ mode }) => { find: /^@actual-app\/crdt(\/.*)?$/, replacement: path.resolve(crdtDir, 'src') + '$1', }, + { + find: '@actual-app/query', + replacement: path.resolve(__dirname, '../query/src'), + }, + { + find: '@actual-app/shared-types', + replacement: path.resolve(__dirname, '../shared-types/src'), + }, ], }, plugins: [ diff --git a/packages/loot-core/vite.config.ts b/packages/loot-core/vite.config.ts index 067ad50fa8..03d384bbad 100644 --- a/packages/loot-core/vite.config.ts +++ b/packages/loot-core/vite.config.ts @@ -77,6 +77,14 @@ export default defineConfig(({ mode }) => { find: /^@actual-app\/crdt(\/.*)?$/, replacement: path.resolve(crdtDir, 'src') + '$1', }, + { + find: '@actual-app/query', + replacement: path.resolve(__dirname, '../query/src'), + }, + { + find: '@actual-app/shared-types', + replacement: path.resolve(__dirname, '../shared-types/src'), + }, ], }, define: { diff --git a/packages/loot-core/vite.desktop.config.ts b/packages/loot-core/vite.desktop.config.ts index dbd46f022e..71d710074f 100644 --- a/packages/loot-core/vite.desktop.config.ts +++ b/packages/loot-core/vite.desktop.config.ts @@ -50,6 +50,14 @@ export default defineConfig(({ mode }) => { find: /^@actual-app\/crdt(\/.*)?$/, replacement: path.resolve(crdtDir, 'src') + '$1', }, + { + find: '@actual-app/query', + replacement: path.resolve(__dirname, '../query/src'), + }, + { + find: '@actual-app/shared-types', + replacement: path.resolve(__dirname, '../shared-types/src'), + }, ], }, plugins: [ diff --git a/packages/plugins-core/package.json b/packages/plugins-core/package.json new file mode 100644 index 0000000000..2198f3903b --- /dev/null +++ b/packages/plugins-core/package.json @@ -0,0 +1,59 @@ +{ + "name": "@actual-app/plugins-core", + "private": true, + "version": "0.1.0", + "description": "Core plugin system for Actual Budget", + "main": "src/client.ts", + "types": "src/client.ts", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint src/ tests/ --ext .ts,.tsx", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.4", + "typescript": "^5.5.4", + "typescript-eslint": "^8.18.1", + "typescript-strict-plugin": "^2.4.4", + "vite": "^6.2.0", + "vite-plugin-dts": "^4.5.3" + }, + "exports": { + "./server": { + "types": "./src/server.ts", + "development": "./src/server.ts", + "import": "./build/server.js", + "require": "./build/server.cjs" + }, + "./client": { + "types": "./src/client.ts", + "development": "./src/client.ts", + "import": "./build/client.js", + "require": "./build/client.cjs" + }, + "./BasicModalComponents": { + "types": "./src/BasicModalComponents.tsx", + "import": "./build/BasicModalComponents.js", + "development": "./src/BasicModalComponents.tsx" + }, + "./types/*": { + "types": "./src/types/*.ts", + "development": "./src/types/*.ts" + }, + "./query": { + "types": "./src/query/index.ts", + "development": "./src/query/index.ts" + }, + "./src/*": "./src/*" + }, + "peerDependencies": { + "i18next": "^25.2.1", + "react": "19.1.0", + "react-aria-components": "^1.7.1", + "react-dom": "19.1.0", + "react-i18next": "^16.0.0" + } +} diff --git a/packages/plugins-core/src/BasicModalComponents.tsx b/packages/plugins-core/src/BasicModalComponents.tsx new file mode 100644 index 0000000000..f98f0ec673 --- /dev/null +++ b/packages/plugins-core/src/BasicModalComponents.tsx @@ -0,0 +1,228 @@ +import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { + Button, + CSSProperties, + Input, + styles, + SvgLogo, + View, +} from '@actual-app/components'; +import { SvgDelete } from '@actual-app/components/icons/v0'; + +type ModalButtonsProps = { + style?: CSSProperties; + leftContent?: ReactNode; + focusButton?: boolean; + children: ReactNode; +}; + +export const ModalButtons = ({ + style, + leftContent, + focusButton = false, + children, +}: ModalButtonsProps) => { + const containerRef = useRef(null); + + useEffect(() => { + if (focusButton && containerRef.current) { + const button = containerRef.current.querySelector( + 'button:not([data-hidden])', + ); + + if (button) { + button.focus(); + } + } + }, [focusButton]); + + return ( + + {leftContent} + + {children} + + ); +}; + +type ModalHeaderProps = { + leftContent?: ReactNode; + showLogo?: boolean; + title?: ReactNode; + rightContent?: ReactNode; +}; + +export function ModalHeader({ + leftContent, + showLogo, + title, + rightContent, +}: ModalHeaderProps) { + return ( + + + {leftContent} + + + {(title || showLogo) && ( + + {showLogo && ( + + )} + {title && + (typeof title === 'string' || typeof title === 'number' ? ( + + ) : ( + title + ))} + + )} + + {rightContent && ( + + {rightContent} + + )} + + ); +} + +type ModalTitleProps = { + title: string; + isEditable?: boolean; + getStyle?: (isEditing: boolean) => CSSProperties; + onEdit?: (isEditing: boolean) => void; + onTitleUpdate?: (newName: string) => void; +}; + +export function ModalTitle({ + title, + isEditable, + getStyle, + onTitleUpdate, +}: ModalTitleProps) { + const [isEditing, setIsEditing] = useState(false); + + const onTitleClick = () => { + if (isEditable) { + setIsEditing(true); + } + }; + + const _onTitleUpdate = (newTitle: string) => { + if (newTitle !== title) { + onTitleUpdate?.(newTitle); + } + setIsEditing(false); + }; + + const inputRef = useRef(null); + useEffect(() => { + if (isEditing) { + if (inputRef.current) { + inputRef.current.scrollLeft = 0; + } + } + }, [isEditing]); + + const style = getStyle?.(isEditing); + + return isEditing ? ( + { + if (e.key === 'Enter') { + e.preventDefault(); + _onTitleUpdate?.(e.currentTarget.value); + } + }} + /> + ) : ( + + + {title} + + + ); +} + +type ModalCloseButtonProps = { + onPress?: () => void; + style?: CSSProperties; +}; + +export function ModalCloseButton({ onPress, style }: ModalCloseButtonProps) { + const { t } = useTranslation(); + return ( + + ); +} diff --git a/packages/plugins-core/src/client.ts b/packages/plugins-core/src/client.ts new file mode 100644 index 0000000000..c0f1ebca79 --- /dev/null +++ b/packages/plugins-core/src/client.ts @@ -0,0 +1,37 @@ +// Modal Components (client-side only) +export { + ModalTitle, + ModalButtons, + ModalHeader, + ModalCloseButton, +} from './BasicModalComponents'; + +// Client-side middleware +export { initializePlugin } from './middleware'; + +// Icons, styles, theme (client-side only) +export * from '@actual-app/components'; + +// Client-side hooks (React hooks) +export { useReport } from './utils'; + +// Query System (also needed on client-side for components) +export * from '@actual-app/query'; + +// Spreadsheet types and utilities (client-side only) +export * from '@actual-app/shared-types/spreadsheet'; + +// Client-side plugin types +export type { + ActualPlugin, + ActualPluginInitialized, + ThemeColorOverrides, + HostContext, +} from './types/actualPlugin'; + +export type * from '@actual-app/components/props/modalProps'; + +export type { + ActualPluginToolkit, + ActualPluginToolkitFunctions, +} from './types/toolkit'; diff --git a/packages/plugins-core/src/index.ts b/packages/plugins-core/src/index.ts new file mode 100644 index 0000000000..0508ff78a2 --- /dev/null +++ b/packages/plugins-core/src/index.ts @@ -0,0 +1,13 @@ +/** + * Main Entry Point for @actual-app/plugins-core + * + * Re-exports everything from both server and client exports. + * `server` must be used in `loot-core` + * `client` must be used in `desktop-client` + */ + +// Re-export all server-safe exports +export * from './server'; + +// Re-export all client-only exports +export * from './client'; diff --git a/packages/plugins-core/src/middleware.ts b/packages/plugins-core/src/middleware.ts new file mode 100644 index 0000000000..0b857c1dd2 --- /dev/null +++ b/packages/plugins-core/src/middleware.ts @@ -0,0 +1,105 @@ +import React, { ReactElement } from 'react'; +import { initReactI18next } from 'react-i18next'; + +import type { BasicModalProps } from '@actual-app/components'; +import ReactDOM from 'react-dom/client'; + +import { + ActualPlugin, + ActualPluginInitialized, + SlotLocations, +} from './types/actualPlugin'; + +const containerRoots = new WeakMap(); + +function getOrCreateRoot(container: HTMLElement) { + let root = containerRoots.get(container); + if (!root) { + root = ReactDOM.createRoot(container); + containerRoots.set(container, root); + } + return root; +} + +export function initializePlugin( + plugin: ActualPlugin, +): ActualPluginInitialized { + const originalActivate = plugin.activate; + + const newPlugin: ActualPluginInitialized = { + ...plugin, + initialized: true, + activate: context => { + context.i18nInstance.use(initReactI18next); + + const wrappedContext = { + ...context, + + // Database provided by host + db: context.db, + + // Query builder passed through directly + q: context.q, + + registerSlotContent(position: SlotLocations, element: ReactElement) { + return context.registerSlotContent(position, container => { + const root = getOrCreateRoot(container); + root.render(element); + return () => root.unmount(); + }); + }, + + pushModal(element: ReactElement, modalProps?: BasicModalProps) { + context.pushModal(container => { + const root = getOrCreateRoot(container); + root.render(element); + return () => root.unmount(); + }, modalProps); + }, + + registerRoute(path: string, element: ReactElement) { + return context.registerRoute(path, container => { + const root = getOrCreateRoot(container); + root.render(element); + return () => root.unmount(); + }); + }, + + registerDashboardWidget( + widgetType: string, + displayName: string, + element: ReactElement, + options?: { + defaultWidth?: number; + defaultHeight?: number; + minWidth?: number; + minHeight?: number; + }, + ) { + return context.registerDashboardWidget( + widgetType, + displayName, + container => { + const root = getOrCreateRoot(container); + root.render(element); + return () => root.unmount(); + }, + options, + ); + }, + + // Theme methods - passed through from host context + addTheme: context.addTheme, + overrideTheme: context.overrideTheme, + + // Report and spreadsheet utilities - passed through from host context + createSpreadsheet: context.createSpreadsheet, + makeFilters: context.makeFilters, + }; + + originalActivate(wrappedContext); + }, + }; + + return newPlugin; +} diff --git a/packages/plugins-core/src/server.ts b/packages/plugins-core/src/server.ts new file mode 100644 index 0000000000..3002ff2c67 --- /dev/null +++ b/packages/plugins-core/src/server.ts @@ -0,0 +1,62 @@ +/** + * Server-Only Exports for @actual-app/plugins-core + * + * This file contains only types and utilities that can be used in server environments + * (Web Workers, Node.js) without any DOM dependencies or React components. + */ + +// Database types +export type { + SqlParameter, + DatabaseQueryResult, + DatabaseRow, + DatabaseSelectResult, + DatabaseResult, + DatabaseOperation, + PluginMetadata, +} from '@actual-app/query/database'; + +// Plugin file types +export type { PluginFile, PluginFileCollection } from './types/plugin-files'; + +// AQL query result types +export type { + AQLQueryResult, + AQLQueryOptions, +} from '@actual-app/query/aql-result'; + +// Model types (server-safe) +export type { + AccountEntity, + CategoryEntity, + CategoryGroupEntity, + PayeeEntity, + ScheduleEntity, +} from '@actual-app/shared-types/models/index'; + +// Plugin types (server-safe ones) +export type { + PluginDatabase, + PluginSpreadsheet, + PluginBinding, + PluginCellValue, + PluginFilterCondition, + PluginFilterResult, + PluginConditionValue, + PluginMigration, + PluginContext, + ContextEvent, +} from './types/actualPlugin'; + +export type { ActualPluginEntry } from './types/actualPluginEntry'; +export type { ActualPluginManifest } from './types/actualPluginManifest'; + +// Query System (server-safe) +export { + Query, + q, + getPrimaryOrderBy, + type QueryState, + type QueryBuilder, + type ObjectExpression, +} from '@actual-app/query'; diff --git a/packages/plugins-core/src/types/actualPlugin.ts b/packages/plugins-core/src/types/actualPlugin.ts new file mode 100644 index 0000000000..76798d02cb --- /dev/null +++ b/packages/plugins-core/src/types/actualPlugin.ts @@ -0,0 +1,521 @@ +import type { ReactElement } from 'react'; + +import type { BasicModalProps } from '@actual-app/components'; +import type { Query, QueryBuilder } from '@actual-app/query'; +import type { + AccountEntity, + CategoryEntity, + CategoryGroupEntity, + PayeeEntity, + ScheduleEntity, +} from '@actual-app/shared-types'; +import type { i18n } from 'i18next'; + +export type SlotLocations = + | 'main-menu' + | 'more-menu' + | 'before-accounts' + | 'after-accounts' + | 'topbar'; + +// Define condition value types for filtering +export type PluginConditionValue = + | string + | number + | boolean + | null + | Array + | { num1: number; num2: number }; + +export type PluginFilterCondition = { + field: string; + op: string; + value: PluginConditionValue; + type?: string; + customName?: string; +}; + +export type PluginFilterResult = { + filters: Record; +}; + +// Simple color mapping type for theme methods +export type ThemeColorOverrides = { + // Page colors + pageBackground?: string; + pageBackgroundModalActive?: string; + pageBackgroundTopLeft?: string; + pageBackgroundBottomRight?: string; + pageBackgroundLineTop?: string; + pageBackgroundLineMid?: string; + pageBackgroundLineBottom?: string; + pageText?: string; + pageTextLight?: string; + pageTextSubdued?: string; + pageTextDark?: string; + pageTextPositive?: string; + pageTextLink?: string; + pageTextLinkLight?: string; + + // Card colors + cardBackground?: string; + cardBorder?: string; + cardShadow?: string; + + // Table colors + tableBackground?: string; + tableRowBackgroundHover?: string; + tableText?: string; + tableTextLight?: string; + tableTextSubdued?: string; + tableTextSelected?: string; + tableTextHover?: string; + tableTextInactive?: string; + tableHeaderText?: string; + tableHeaderBackground?: string; + tableBorder?: string; + tableBorderSelected?: string; + tableBorderHover?: string; + tableBorderSeparator?: string; + tableRowBackgroundHighlight?: string; + tableRowBackgroundHighlightText?: string; + tableRowHeaderBackground?: string; + tableRowHeaderText?: string; + + // Sidebar colors + sidebarBackground?: string; + sidebarItemBackgroundPending?: string; + sidebarItemBackgroundPositive?: string; + sidebarItemBackgroundFailed?: string; + sidebarItemBackgroundHover?: string; + sidebarItemAccentSelected?: string; + sidebarItemText?: string; + sidebarItemTextSelected?: string; + + // Menu colors + menuBackground?: string; + menuItemBackground?: string; + menuItemBackgroundHover?: string; + menuItemText?: string; + menuItemTextHover?: string; + menuItemTextSelected?: string; + menuItemTextHeader?: string; + menuBorder?: string; + menuBorderHover?: string; + menuKeybindingText?: string; + menuAutoCompleteBackground?: string; + menuAutoCompleteBackgroundHover?: string; + menuAutoCompleteText?: string; + menuAutoCompleteTextHover?: string; + menuAutoCompleteTextHeader?: string; + menuAutoCompleteItemTextHover?: string; + menuAutoCompleteItemText?: string; + + // Modal colors + modalBackground?: string; + modalBorder?: string; + + // Mobile colors + mobileHeaderBackground?: string; + mobileHeaderText?: string; + mobileHeaderTextSubdued?: string; + mobileHeaderTextHover?: string; + mobilePageBackground?: string; + mobileNavBackground?: string; + mobileNavItem?: string; + mobileNavItemSelected?: string; + mobileAccountShadow?: string; + mobileAccountText?: string; + mobileTransactionSelected?: string; + mobileViewTheme?: string; + mobileConfigServerViewTheme?: string; + + // Markdown colors + markdownNormal?: string; + markdownDark?: string; + markdownLight?: string; + + // Button colors - Menu buttons + buttonMenuText?: string; + buttonMenuTextHover?: string; + buttonMenuBackground?: string; + buttonMenuBackgroundHover?: string; + buttonMenuBorder?: string; + buttonMenuSelectedText?: string; + buttonMenuSelectedTextHover?: string; + buttonMenuSelectedBackground?: string; + buttonMenuSelectedBackgroundHover?: string; + buttonMenuSelectedBorder?: string; + + // Button colors - Primary buttons + buttonPrimaryText?: string; + buttonPrimaryTextHover?: string; + buttonPrimaryBackground?: string; + buttonPrimaryBackgroundHover?: string; + buttonPrimaryBorder?: string; + buttonPrimaryShadow?: string; + buttonPrimaryDisabledText?: string; + buttonPrimaryDisabledBackground?: string; + buttonPrimaryDisabledBorder?: string; + + // Button colors - Normal buttons + buttonNormalText?: string; + buttonNormalTextHover?: string; + buttonNormalBackground?: string; + buttonNormalBackgroundHover?: string; + buttonNormalBorder?: string; + buttonNormalShadow?: string; + buttonNormalSelectedText?: string; + buttonNormalSelectedBackground?: string; + buttonNormalDisabledText?: string; + buttonNormalDisabledBackground?: string; + buttonNormalDisabledBorder?: string; + + // Button colors - Bare buttons + buttonBareText?: string; + buttonBareTextHover?: string; + buttonBareBackground?: string; + buttonBareBackgroundHover?: string; + buttonBareBackgroundActive?: string; + buttonBareDisabledText?: string; + buttonBareDisabledBackground?: string; + + // Calendar colors + calendarText?: string; + calendarBackground?: string; + calendarItemText?: string; + calendarItemBackground?: string; + calendarSelectedBackground?: string; + calendarCellBackground?: string; + + // Status colors - Notice + noticeBackground?: string; + noticeBackgroundLight?: string; + noticeBackgroundDark?: string; + noticeText?: string; + noticeTextLight?: string; + noticeTextDark?: string; + noticeTextMenu?: string; + noticeTextMenuHover?: string; + noticeBorder?: string; + + // Status colors - Warning + warningBackground?: string; + warningText?: string; + warningTextLight?: string; + warningTextDark?: string; + warningBorder?: string; + + // Status colors - Error + errorBackground?: string; + errorText?: string; + errorTextDark?: string; + errorTextDarker?: string; + errorTextMenu?: string; + errorBorder?: string; + + // Status colors - Upcoming + upcomingBackground?: string; + upcomingText?: string; + upcomingBorder?: string; + + // Form colors + formLabelText?: string; + formLabelBackground?: string; + formInputBackground?: string; + formInputBackgroundSelected?: string; + formInputBackgroundSelection?: string; + formInputBorder?: string; + formInputTextReadOnlySelection?: string; + formInputBorderSelected?: string; + formInputText?: string; + formInputTextSelected?: string; + formInputTextPlaceholder?: string; + formInputTextPlaceholderSelected?: string; + formInputTextSelection?: string; + formInputShadowSelected?: string; + formInputTextHighlight?: string; + + // Checkbox colors + checkboxText?: string; + checkboxBackgroundSelected?: string; + checkboxBorderSelected?: string; + checkboxShadowSelected?: string; + checkboxToggleBackground?: string; + checkboxToggleBackgroundSelected?: string; + checkboxToggleDisabled?: string; + + // Pill colors + pillBackground?: string; + pillBackgroundLight?: string; + pillText?: string; + pillTextHighlighted?: string; + pillBorder?: string; + pillBorderDark?: string; + pillBackgroundSelected?: string; + pillTextSelected?: string; + pillBorderSelected?: string; + pillTextSubdued?: string; + + // Reports colors + reportsRed?: string; + reportsBlue?: string; + reportsGreen?: string; + reportsGray?: string; + reportsLabel?: string; + reportsInnerLabel?: string; + + // Note tag colors + noteTagBackground?: string; + noteTagBackgroundHover?: string; + noteTagText?: string; + + // Budget colors + budgetCurrentMonth?: string; + budgetOtherMonth?: string; + budgetHeaderCurrentMonth?: string; + budgetHeaderOtherMonth?: string; + + // Floating action bar colors + floatingActionBarBackground?: string; + floatingActionBarBorder?: string; + floatingActionBarText?: string; + + // Tooltip colors + tooltipText?: string; + tooltipBackground?: string; + tooltipBorder?: string; + + // Custom colors (plugin-specific) + [customColor: `custom-${string}`]: string; +}; + +export interface PluginDatabase { + runQuery( + sql: string, + params?: (string | number)[], + fetchAll?: boolean, + ): Promise; + + execQuery(sql: string): void; + + transaction(fn: () => void): void; + + getMigrationState(): Promise; + + setMetadata(key: string, value: string): Promise; + + getMetadata(key: string): Promise; + + /** + * Execute an AQL (Actual Query Language) query. + * This provides a higher-level abstraction over SQL that's consistent with Actual Budget's query system. + * + * @param query - The AQL query (can be a PluginQuery object or serialized PluginQueryState) + * @param options - Optional parameters for the query + * @param options.target - Target database: 'plugin' for plugin tables, 'host' for main app tables. Defaults to 'plugin' + * @param options.params - Named parameters for the query + * @returns Promise that resolves to the query result with data and dependencies + */ + aql( + query: Query, + options?: { + target?: 'plugin' | 'host'; + params?: Record; + }, + ): Promise<{ data: unknown; dependencies: string[] }>; +} + +export type PluginBinding = string | { name: string; query?: Query }; + +export type PluginCellValue = { name: string; value: unknown | null }; + +export interface PluginSpreadsheet { + /** + * Bind to a cell and observe changes + * @param sheetName - Name of the sheet (optional, defaults to global) + * @param binding - Cell binding (string name or object with name and optional query) + * @param callback - Function called when cell value changes + * @returns Cleanup function to stop observing + */ + bind( + sheetName: string | undefined, + binding: PluginBinding, + callback: (node: PluginCellValue) => void, + ): () => void; + + /** + * Get a cell value directly + * @param sheetName - Name of the sheet + * @param name - Cell name + * @returns Promise that resolves to the cell value + */ + get(sheetName: string, name: string): Promise; + + /** + * Get all cell names in a sheet + * @param sheetName - Name of the sheet + * @returns Promise that resolves to array of cell names + */ + getCellNames(sheetName: string): Promise; + + /** + * Create a query in a sheet + * @param sheetName - Name of the sheet + * @param name - Query name + * @param query - The query to create + * @returns Promise that resolves when query is created + */ + createQuery(sheetName: string, name: string, query: Query): Promise; +} + +export type PluginMigration = [ + timestamp: number, + name: string, + upCommand: string, + downCommand: string, +]; + +// Plugin context type for easier reuse +export type PluginContext = Omit< + HostContext, + | 'registerSlotContent' + | 'pushModal' + | 'registerRoute' + | 'registerDashboardWidget' +> & { + registerSlotContent: ( + location: SlotLocations, + element: ReactElement, + ) => string; + pushModal: (element: ReactElement, modalProps?: BasicModalProps) => void; + registerRoute: (path: string, routeElement: ReactElement) => string; + + // Dashboard widget registration - wrapped for JSX elements + registerDashboardWidget: ( + widgetType: string, + displayName: string, + element: ReactElement, + options?: { + defaultWidth?: number; + defaultHeight?: number; + minWidth?: number; + minHeight?: number; + }, + ) => string; + + // Theme methods - simple and direct + addTheme: ( + themeId: string, + displayName: string, + colorOverrides: ThemeColorOverrides, + options?: { + baseTheme?: 'light' | 'dark' | 'midnight'; + description?: string; + }, + ) => void; + + overrideTheme: ( + themeId: 'light' | 'dark' | 'midnight' | string, + colorOverrides: ThemeColorOverrides, + ) => void; + + db?: PluginDatabase; + q: QueryBuilder; + + // Report and spreadsheet utilities + createSpreadsheet: () => PluginSpreadsheet; + + makeFilters: ( + conditions: Array, + ) => Promise; +}; + +export interface ActualPlugin { + name: string; + version: string; + uninstall: () => void; + migrations?: () => PluginMigration[]; + activate: (context: PluginContext) => void; +} + +export type ActualPluginInitialized = Omit & { + initialized: true; + activate: (context: HostContext & { db: PluginDatabase }) => void; +}; + +export interface ContextEvent { + payees: { payees: PayeeEntity[] }; + categories: { categories: CategoryEntity[]; groups: CategoryGroupEntity[] }; + accounts: { accounts: AccountEntity[] }; + schedules: { schedules: ScheduleEntity[] }; +} + +export interface HostContext { + navigate: (routePath: string) => void; + + pushModal: ( + parameter: (container: HTMLDivElement) => void | (() => void), + modalProps?: BasicModalProps, + ) => void; + popModal: () => void; + + registerRoute: ( + path: string, + routeElement: (container: HTMLDivElement) => void | (() => void), + ) => string; + unregisterRoute: (id: string) => void; + + registerSlotContent: ( + location: SlotLocations, + parameter: (container: HTMLDivElement) => void | (() => void), + ) => string; + unregisterSlotContent: (id: string) => void; + + on: ( + eventType: K, + callback: (data: ContextEvent[K]) => void, + ) => void; + + // Dashboard widget methods + registerDashboardWidget: ( + widgetType: string, + displayName: string, + renderWidget: (container: HTMLDivElement) => void | (() => void), + options?: { + defaultWidth?: number; + defaultHeight?: number; + minWidth?: number; + minHeight?: number; + }, + ) => string; + unregisterDashboardWidget: (id: string) => void; + + // Theme methods + addTheme: ( + themeId: string, + displayName: string, + colorOverrides: ThemeColorOverrides, + options?: { + baseTheme?: 'light' | 'dark' | 'midnight'; + description?: string; + }, + ) => void; + + overrideTheme: ( + themeId: 'light' | 'dark' | 'midnight' | string, + colorOverrides: ThemeColorOverrides, + ) => void; + + // Query builder provided by host (loot-core's q function) + q: QueryBuilder; + + // Report and spreadsheet utilities for dashboard widgets + createSpreadsheet: () => PluginSpreadsheet; + + makeFilters: ( + conditions: Array, + ) => Promise; + + i18nInstance: i18n; +} diff --git a/packages/plugins-core/src/types/actualPluginEntry.ts b/packages/plugins-core/src/types/actualPluginEntry.ts new file mode 100644 index 0000000000..11d04ba973 --- /dev/null +++ b/packages/plugins-core/src/types/actualPluginEntry.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +import { ActualPluginInitialized } from './actualPlugin'; + +export type ActualPluginEntry = () => ActualPluginInitialized; diff --git a/packages/plugins-core/src/types/actualPluginManifest.ts b/packages/plugins-core/src/types/actualPluginManifest.ts new file mode 100644 index 0000000000..3871ead2d3 --- /dev/null +++ b/packages/plugins-core/src/types/actualPluginManifest.ts @@ -0,0 +1,11 @@ +export interface ActualPluginManifest { + url: string; + name: string; + version: string; + enabled?: boolean; + description?: string; + pluginType: 'server' | 'client'; + minimumActualVersion: string; + author: string; + plugin?: Blob; +} diff --git a/packages/plugins-core/src/types/plugin-files.ts b/packages/plugins-core/src/types/plugin-files.ts new file mode 100644 index 0000000000..24f7fac57c --- /dev/null +++ b/packages/plugins-core/src/types/plugin-files.ts @@ -0,0 +1,18 @@ +/** + * Plugin File Types + * + * Types for plugin file operations and storage. + */ + +/** + * Represents a single file within a plugin package + */ +export interface PluginFile { + name: string; + content: string; +} + +/** + * Collection of files that make up a plugin + */ +export type PluginFileCollection = PluginFile[]; diff --git a/packages/plugins-core/src/types/toolkit.ts b/packages/plugins-core/src/types/toolkit.ts new file mode 100644 index 0000000000..734a676e68 --- /dev/null +++ b/packages/plugins-core/src/types/toolkit.ts @@ -0,0 +1,7 @@ +export type ActualPluginToolkitFunctions = { + pushModal: (modalName: string, options?: unknown) => void; +}; + +export type ActualPluginToolkit = { + functions: ActualPluginToolkitFunctions; +}; diff --git a/packages/plugins-core/src/utils.ts b/packages/plugins-core/src/utils.ts new file mode 100644 index 0000000000..694887c161 --- /dev/null +++ b/packages/plugins-core/src/utils.ts @@ -0,0 +1,24 @@ +import { useState, useEffect } from 'react'; + +import { PluginSpreadsheet } from './types/actualPlugin'; + +/** + * useReport hook for plugins to manage spreadsheet data + * This is similar to the useReport hook in desktop-client but adapted for plugins + */ +export function useReport( + sheetName: string, + getData: ( + spreadsheet: PluginSpreadsheet, + setData: (results: T) => void, + ) => Promise, + spreadsheet: PluginSpreadsheet, +): T | null { + const [results, setResults] = useState(null); + + useEffect(() => { + getData(spreadsheet, results => setResults(results)); + }, [getData, spreadsheet]); + + return results; +} diff --git a/packages/plugins-core/tsconfig.json b/packages/plugins-core/tsconfig.json new file mode 100644 index 0000000000..5307b65f97 --- /dev/null +++ b/packages/plugins-core/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "baseUrl": ".", + "paths": { + "@actual-app/shared-types": ["../shared-types/src/index.ts"], + "@actual-app/shared-types/*": ["../shared-types/src/*"], + "@actual-app/shared-types/models": [ + "../shared-types/src/models/index.ts" + ], + "@actual-app/query": ["../query/src/index.ts"], + "@actual-app/query/*": ["../query/src/*"], + "@actual-app/components": ["../component-library/src/index.ts"], + "@actual-app/components/*": ["../component-library/src/*"] + } + }, + "include": ["src"] +} diff --git a/packages/plugins-core/vite.config.mts b/packages/plugins-core/vite.config.mts new file mode 100644 index 0000000000..0c385246c2 --- /dev/null +++ b/packages/plugins-core/vite.config.mts @@ -0,0 +1,55 @@ +import { resolve } from 'path'; + +import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; + +export default defineConfig({ + resolve: { + alias: { + '@actual-app/shared-types': resolve(__dirname, '../shared-types/src'), + '@actual-app/query': resolve(__dirname, '../query/src'), + '@actual-app/components': resolve(__dirname, '../component-library/src'), + }, + }, + build: { + outDir: 'build', + lib: { + entry: { + server: 'src/server.ts', + client: 'src/client.ts', + }, + name: '@actual-app/plugins-core', + fileName: (format, entryName) => + format === 'es' ? `${entryName}.js` : `${entryName}.cjs`, + formats: ['es', 'cjs'], + }, + rollupOptions: { + external: [ + 'react', + 'react-dom', + 'i18next', + 'react-i18next', + 'react-aria-components', + '@emotion/css', + 'usehooks-ts', + ], + output: { + globals: { + react: 'React', + 'react-dom': 'ReactDOM', + i18next: 'i18next', + }, + }, + }, + }, + plugins: [ + dts({ + insertTypesEntry: true, + outDir: 'build', + include: ['src/**/*'], + exclude: ['src/**/*.test.ts', 'src/**/*.test.tsx'], + rollupTypes: false, + copyDtsFiles: false, + }), + ], +}); diff --git a/packages/query/package.json b/packages/query/package.json new file mode 100644 index 0000000000..c6af2f5cd4 --- /dev/null +++ b/packages/query/package.json @@ -0,0 +1,36 @@ +{ + "name": "@actual-app/query", + "version": "0.0.1", + "private": true, + "description": "Query system and database utilities for Actual Budget", + "license": "MIT", + "main": "src/index.ts", + "types": "src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "development": "./src/index.ts" + }, + "./database": { + "types": "./src/database.ts", + "development": "./src/database.ts" + }, + "./aql-result": { + "types": "./src/aql-result.ts", + "development": "./src/aql-result.ts" + }, + "./spreadsheet": { + "types": "./src/spreadsheet.ts", + "development": "./src/spreadsheet.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "typescript": "^5.5.4" + }, + "dependencies": { + "@actual-app/shared-types": "workspace:^" + } +} diff --git a/packages/query/src/aql-result.ts b/packages/query/src/aql-result.ts new file mode 100644 index 0000000000..ec03419f2e --- /dev/null +++ b/packages/query/src/aql-result.ts @@ -0,0 +1,27 @@ +/** + * AQL Query Result Types + * + * Types for AQL (Advanced Query Language) query results. + */ + +import { DatabaseRow } from './database'; + +/** + * Result of an AQL query operation + */ +export interface AQLQueryResult { + /** The actual data returned by the query */ + data: DatabaseRow[] | DatabaseRow | null; + /** List of table/field dependencies detected during query execution */ + dependencies: string[]; +} + +/** + * Options for AQL query execution + */ +export interface AQLQueryOptions { + /** Target for the query - 'plugin' uses plugin DB, 'host' uses main DB */ + target?: 'plugin' | 'host'; + /** Parameters to be passed to the query */ + params?: Record; +} diff --git a/packages/query/src/database.ts b/packages/query/src/database.ts new file mode 100644 index 0000000000..31c30975a2 --- /dev/null +++ b/packages/query/src/database.ts @@ -0,0 +1,51 @@ +/** + * Plugin Database Types + * + * Shared types for database operations between plugins and the host application. + */ + +/** + * Parameters that can be passed to SQL queries + */ +export type SqlParameter = string | number | boolean | null | Buffer; + +/** + * Result of a database query operation (INSERT, UPDATE, DELETE) + */ +export interface DatabaseQueryResult { + changes: number; + insertId?: number; +} + +/** + * Row returned from a database SELECT query + */ +export type DatabaseRow = Record; + +/** + * Result of a database SELECT query + */ +export type DatabaseSelectResult = DatabaseRow[]; + +/** + * Union type for all possible database query results + */ +export type DatabaseResult = DatabaseQueryResult | DatabaseSelectResult; + +/** + * Database transaction operation + */ +export interface DatabaseOperation { + type: 'exec' | 'query'; + sql: string; + params?: SqlParameter[]; + fetchAll?: boolean; // Only used for query operations +} + +/** + * Plugin database metadata key-value pair + */ +export interface PluginMetadata { + key: string; + value: string; +} diff --git a/packages/query/src/index.ts b/packages/query/src/index.ts new file mode 100644 index 0000000000..759448497d --- /dev/null +++ b/packages/query/src/index.ts @@ -0,0 +1,179 @@ +/** + * Query System - Single Source of Truth + * + * This is the main query implementation used by both loot-core and plugins. + * No more conversion functions needed! + */ + +import { + ObjectExpression, + IQuery, + QueryState, + WithRequired, +} from '@actual-app/shared-types'; + +export type { ObjectExpression, IQuery, QueryState }; + +export class Query implements IQuery { + state: QueryState; + + constructor(state: WithRequired, 'table'>) { + this.state = { + tableOptions: state.tableOptions || {}, + filterExpressions: state.filterExpressions || [], + selectExpressions: state.selectExpressions || [], + groupExpressions: state.groupExpressions || [], + orderExpressions: state.orderExpressions || [], + calculation: false, + rawMode: false, + withDead: false, + validateRefs: true, + limit: null, + offset: null, + ...state, + }; + } + + filter(expr: ObjectExpression) { + return new Query({ + ...this.state, + filterExpressions: [...this.state.filterExpressions, expr], + }); + } + + unfilter(exprs?: Array) { + // Remove all filters if no arguments are passed + if (!exprs) { + return new Query({ + ...this.state, + filterExpressions: [], + }); + } + + const exprSet = new Set(exprs); + return new Query({ + ...this.state, + filterExpressions: this.state.filterExpressions.filter( + expr => !exprSet.has(Object.keys(expr)[0]), + ), + }); + } + + select( + exprs: + | Array + | ObjectExpression + | string + | '*' + | ['*'] = [], + ) { + if (!Array.isArray(exprs)) { + exprs = [exprs]; + } + + return new Query({ + ...this.state, + selectExpressions: exprs, + calculation: false, + }); + } + + calculate(expr: ObjectExpression | string) { + return new Query({ + ...this.state, + selectExpressions: [{ result: expr }], + calculation: true, + }); + } + + groupBy(exprs: ObjectExpression | string | Array) { + if (!Array.isArray(exprs)) { + exprs = [exprs]; + } + + return new Query({ + ...this.state, + groupExpressions: [...this.state.groupExpressions, ...exprs], + }); + } + + orderBy(exprs: ObjectExpression | string | Array) { + if (!Array.isArray(exprs)) { + exprs = [exprs]; + } + + return new Query({ + ...this.state, + orderExpressions: [...this.state.orderExpressions, ...exprs], + }); + } + + limit(num: number) { + return new Query({ ...this.state, limit: num }); + } + + offset(num: number) { + return new Query({ ...this.state, offset: num }); + } + + raw() { + return new Query({ ...this.state, rawMode: true }); + } + + withDead() { + return new Query({ ...this.state, withDead: true }); + } + + withoutValidatedRefs() { + return new Query({ ...this.state, validateRefs: false }); + } + + options(opts: Record) { + return new Query({ ...this.state, tableOptions: opts }); + } + + reset() { + return q(this.state.table); + } + + serialize() { + return this.state; + } + + serializeAsString() { + return JSON.stringify(this.serialize()); + } +} + +/** + * Query builder function - creates a new Query for the given table + */ +export function q(table: QueryState['table']) { + return new Query({ table }); +} + +export type { QueryBuilder } from '@actual-app/shared-types'; + +/** + * Helper function to get primary order by clause + */ +export function getPrimaryOrderBy( + query: Query, + defaultOrderBy: ObjectExpression | null, +) { + const orderExprs = query.serialize().orderExpressions; + if (orderExprs.length === 0) { + if (defaultOrderBy) { + return { order: 'asc', ...defaultOrderBy }; + } + return null; + } + + const firstOrder = orderExprs[0]; + if (typeof firstOrder === 'string') { + return { field: firstOrder, order: 'asc' }; + } + // Handle this form: { field: 'desc' } + const [field] = Object.keys(firstOrder); + return { field, order: firstOrder[field] }; +} diff --git a/packages/query/tsconfig.json b/packages/query/tsconfig.json new file mode 100644 index 0000000000..add71afe3d --- /dev/null +++ b/packages/query/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2021", + "noEmit": false, + "declaration": true, + "outDir": "dist", + "declarationDir": "@types", + "paths": { + "@actual-app/shared-types": ["../shared-types/src/index.ts"], + "@actual-app/shared-types/*": ["../shared-types/src/*"], + "@actual-app/shared-types/models": ["../shared-types/src/models/index.ts"] + } + }, + "include": ["."], + "exclude": ["**/node_modules/*", "dist", "@types", "*.test.ts"] +} diff --git a/packages/shared-types/package.json b/packages/shared-types/package.json new file mode 100644 index 0000000000..cb3cff4610 --- /dev/null +++ b/packages/shared-types/package.json @@ -0,0 +1,29 @@ +{ + "name": "@actual-app/shared-types", + "version": "0.0.1", + "private": true, + "description": "Shared type definitions for Actual Budget", + "license": "MIT", + "main": "src/index.ts", + "types": "src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "development": "./src/index.ts" + }, + "./models/*": { + "types": "./src/models/*.ts", + "development": "./src/models/*.ts" + }, + "./modalProps": { + "types": "./src/modalProps.ts", + "development": "./src/modalProps.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "typescript": "^5.5.4" + } +} diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts new file mode 100644 index 0000000000..25771516e6 --- /dev/null +++ b/packages/shared-types/src/index.ts @@ -0,0 +1,8 @@ +// Re-export all model types +export type * from './models'; + +// Export query types +export type * from './query'; + +// Export utility types +export type * from './util'; diff --git a/packages/shared-types/src/models/account.ts b/packages/shared-types/src/models/account.ts new file mode 100644 index 0000000000..b10817fc72 --- /dev/null +++ b/packages/shared-types/src/models/account.ts @@ -0,0 +1,25 @@ +export type AccountEntity = { + id: string; + name: string; + offbudget: 0 | 1; + closed: 0 | 1; + sort_order: number; + last_reconciled: string | null; + tombstone: 0 | 1; +} & (_SyncFields | _SyncFields); + +export type _SyncFields = { + account_id: T extends true ? string : null; + bank: T extends true ? string : null; + bankName: T extends true ? string : null; + bankId: T extends true ? number : null; + mask: T extends true ? string : null; // end of bank account number + official_name: T extends true ? string : null; + balance_current: T extends true ? number : null; + balance_available: T extends true ? number : null; + balance_limit: T extends true ? number : null; + account_sync_source: T extends true ? AccountSyncSource : null; + last_sync: T extends true ? string : null; +}; + +export type AccountSyncSource = 'simpleFin' | 'goCardless' | 'pluggyai'; diff --git a/packages/shared-types/src/models/category-group.ts b/packages/shared-types/src/models/category-group.ts new file mode 100644 index 0000000000..d128cf3070 --- /dev/null +++ b/packages/shared-types/src/models/category-group.ts @@ -0,0 +1,11 @@ +import { CategoryEntity } from './category'; + +export interface CategoryGroupEntity { + id: string; + name: string; + is_income?: boolean; + sort_order?: number; + tombstone?: boolean; + hidden?: boolean; + categories?: CategoryEntity[]; +} diff --git a/packages/shared-types/src/models/category-views.ts b/packages/shared-types/src/models/category-views.ts new file mode 100644 index 0000000000..8b07df21dd --- /dev/null +++ b/packages/shared-types/src/models/category-views.ts @@ -0,0 +1,7 @@ +import { CategoryEntity } from './category'; +import { CategoryGroupEntity } from './category-group'; + +export interface CategoryViews { + grouped: CategoryGroupEntity[]; + list: CategoryEntity[]; +} diff --git a/packages/shared-types/src/models/category.ts b/packages/shared-types/src/models/category.ts new file mode 100644 index 0000000000..5193181401 --- /dev/null +++ b/packages/shared-types/src/models/category.ts @@ -0,0 +1,13 @@ +import { CategoryGroupEntity } from './category-group'; + +export interface CategoryEntity { + id: string; + name: string; + is_income?: boolean; + group: CategoryGroupEntity['id']; + goal_def?: string; + template_settings?: { source: 'notes' | 'ui' }; + sort_order?: number; + tombstone?: boolean; + hidden?: boolean; +} diff --git a/packages/shared-types/src/models/index.ts b/packages/shared-types/src/models/index.ts new file mode 100644 index 0000000000..a7e9f461ea --- /dev/null +++ b/packages/shared-types/src/models/index.ts @@ -0,0 +1,7 @@ +export type * from './account'; +export type * from './payee'; +export type * from './category'; +export type * from './category-group'; +export type * from './category-views'; +export type * from './rule'; +export type * from './schedule'; diff --git a/packages/shared-types/src/models/payee.ts b/packages/shared-types/src/models/payee.ts new file mode 100644 index 0000000000..a559c4b93f --- /dev/null +++ b/packages/shared-types/src/models/payee.ts @@ -0,0 +1,10 @@ +import { AccountEntity } from './account'; + +export interface PayeeEntity { + id: string; + name: string; + transfer_acct?: AccountEntity['id']; + favorite?: boolean; + learn_categories?: boolean; + tombstone?: boolean; +} diff --git a/packages/shared-types/src/models/rule.ts b/packages/shared-types/src/models/rule.ts new file mode 100644 index 0000000000..52d8cdd61f --- /dev/null +++ b/packages/shared-types/src/models/rule.ts @@ -0,0 +1,186 @@ +import type { RecurConfig, ScheduleEntity } from './schedule'; + +export interface NewRuleEntity { + stage: 'pre' | null | 'post'; + conditionsOp: 'or' | 'and'; + conditions: RuleConditionEntity[]; + actions: RuleActionEntity[]; + tombstone?: boolean; +} + +export interface RuleEntity extends NewRuleEntity { + id: string; +} + +export type RuleConditionOp = + | 'is' + | 'isNot' + | 'oneOf' + | 'notOneOf' + | 'isapprox' + | 'isbetween' + | 'gt' + | 'gte' + | 'lt' + | 'lte' + | 'contains' + | 'doesNotContain' + | 'hasTags' + | 'and' + | 'matches' + | 'onBudget' + | 'offBudget'; + +export type FieldValueTypes = { + account: string; + amount: number; + category: string; + date: string | RecurConfig; + notes: string; + payee: string; + payee_name: string; + imported_payee: string; + saved: string; + transfer: boolean; + parent: boolean; + cleared: boolean; + reconciled: boolean; +}; + +type BaseConditionEntity< + Field extends keyof FieldValueTypes, + Op extends RuleConditionOp, +> = { + field: Field; + op: Op; + value: Op extends 'oneOf' | 'notOneOf' + ? Array + : Op extends 'isbetween' + ? { num1: number; num2: number } + : FieldValueTypes[Field]; + options?: { + inflow?: boolean; + outflow?: boolean; + month?: boolean; + year?: boolean; + }; + conditionsOp?: string; + type?: 'id' | 'boolean' | 'date' | 'number' | 'string'; + customName?: string; + queryFilter?: Record; +}; + +export type RuleConditionEntity = + | BaseConditionEntity< + 'account', + | 'is' + | 'isNot' + | 'oneOf' + | 'notOneOf' + | 'contains' + | 'doesNotContain' + | 'matches' + | 'onBudget' + | 'offBudget' + > + | BaseConditionEntity< + 'category', + | 'is' + | 'isNot' + | 'oneOf' + | 'notOneOf' + | 'contains' + | 'doesNotContain' + | 'matches' + > + | BaseConditionEntity< + 'amount', + 'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte' + > + | BaseConditionEntity< + 'date', + 'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte' + > + | BaseConditionEntity< + 'notes', + | 'is' + | 'isNot' + | 'oneOf' + | 'notOneOf' + | 'contains' + | 'doesNotContain' + | 'matches' + | 'hasTags' + > + | BaseConditionEntity< + 'payee', + | 'is' + | 'isNot' + | 'oneOf' + | 'notOneOf' + | 'contains' + | 'doesNotContain' + | 'matches' + > + | BaseConditionEntity< + 'imported_payee', + | 'is' + | 'isNot' + | 'oneOf' + | 'notOneOf' + | 'contains' + | 'doesNotContain' + | 'matches' + > + | BaseConditionEntity<'saved', 'is'> + | BaseConditionEntity<'cleared', 'is'> + | BaseConditionEntity<'reconciled', 'is'>; + +export type RuleActionEntity = + | SetRuleActionEntity + | SetSplitAmountRuleActionEntity + | LinkScheduleRuleActionEntity + | PrependNoteRuleActionEntity + | AppendNoteRuleActionEntity + | DeleteTransactionRuleActionEntity; + +export interface SetRuleActionEntity { + field: string; + op: 'set'; + value: unknown; + options?: { + template?: string; + formula?: string; + splitIndex?: number; + }; + type?: string; +} + +export interface SetSplitAmountRuleActionEntity { + op: 'set-split-amount'; + value: number; + options?: { + splitIndex?: number; + method: 'fixed-amount' | 'fixed-percent' | 'remainder'; + }; +} + +export interface LinkScheduleRuleActionEntity { + op: 'link-schedule'; + value: ScheduleEntity; +} + +export interface PrependNoteRuleActionEntity { + op: 'prepend-notes'; + value: string; +} + +export interface AppendNoteRuleActionEntity { + op: 'append-notes'; + value: string; +} + +export interface DeleteTransactionRuleActionEntity { + op: 'delete-transaction'; + value: string; +} diff --git a/packages/shared-types/src/models/schedule.ts b/packages/shared-types/src/models/schedule.ts new file mode 100644 index 0000000000..16ff20442e --- /dev/null +++ b/packages/shared-types/src/models/schedule.ts @@ -0,0 +1,49 @@ +import type { AccountEntity } from './account'; +import type { PayeeEntity } from './payee'; +import type { RuleConditionEntity, RuleEntity } from './rule'; + +export interface RecurPattern { + value: number; + type: 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'day'; +} + +export interface RecurConfig { + frequency: 'daily' | 'weekly' | 'monthly' | 'yearly'; + interval?: number; + patterns?: RecurPattern[]; + skipWeekend?: boolean; + start: string; + endMode: 'never' | 'after_n_occurrences' | 'on_date'; + endOccurrences?: number; + endDate?: string; + weekendSolveMode?: 'before' | 'after'; +} + +export interface ScheduleEntity { + id: string; + name?: string; + rule: RuleEntity['id']; + next_date: string; + completed: boolean; + posts_transaction: boolean; + tombstone: boolean; + + // These are special fields that are actually pulled from the + // underlying rule + _payee: PayeeEntity['id']; + _account: AccountEntity['id']; + _amount: number | { num1: number; num2: number }; + _amountOp: string; + _date: RecurConfig; + _conditions: RuleConditionEntity[]; + _actions: Array<{ op: unknown }>; +} + +export type DiscoverScheduleEntity = { + id: ScheduleEntity['id']; + account: AccountEntity['id']; + payee: PayeeEntity['id']; + date: ScheduleEntity['_date']; + amount: ScheduleEntity['_amount']; + _conditions: ScheduleEntity['_conditions']; +}; diff --git a/packages/shared-types/src/query.ts b/packages/shared-types/src/query.ts new file mode 100644 index 0000000000..9a1b322f2e --- /dev/null +++ b/packages/shared-types/src/query.ts @@ -0,0 +1,54 @@ +/** + * Query System Types + */ + +export type ObjectExpression = { + [key: string]: ObjectExpression | unknown; +}; + +export type QueryState = { + get table(): string; + get tableOptions(): Readonly>; + get filterExpressions(): ReadonlyArray; + get selectExpressions(): ReadonlyArray; + get groupExpressions(): ReadonlyArray; + get orderExpressions(): ReadonlyArray; + get calculation(): boolean; + get rawMode(): boolean; + get withDead(): boolean; + get validateRefs(): boolean; + get limit(): number | null; + get offset(): number | null; +}; + +export type IQuery = { + state: QueryState; + filter(expr: ObjectExpression): IQuery; + unfilter(exprs?: Array): IQuery; + select( + exprs: + | Array + | ObjectExpression + | string + | '*' + | ['*'], + ): IQuery; + calculate(expr: ObjectExpression | string): IQuery; + groupBy( + exprs: ObjectExpression | string | Array, + ): IQuery; + orderBy( + exprs: ObjectExpression | string | Array, + ): IQuery; + limit(num: number): IQuery; + offset(num: number): IQuery; + raw(): IQuery; + withDead(): IQuery; + withoutValidatedRefs(): IQuery; + options(opts: Record): IQuery; + reset(): IQuery; + serialize(): QueryState; + serializeAsString(): string; +}; + +export type QueryBuilder = (table: string) => IQuery; diff --git a/packages/shared-types/src/spreadsheet.ts b/packages/shared-types/src/spreadsheet.ts new file mode 100644 index 0000000000..85269e8edb --- /dev/null +++ b/packages/shared-types/src/spreadsheet.ts @@ -0,0 +1,127 @@ +/** + * Spreadsheet Types and Utilities + * + * Core spreadsheet schema definitions and binding utilities used across + * the application for managing financial data in sheet-like structures. + */ + +import { IQuery } from './query'; + +export type Spreadsheets = { + account: { + // Common fields + 'uncategorized-amount': number; + 'uncategorized-balance': number; + + // Account fields + balance: number; + [key: `balance-${string}-cleared`]: number | null; + 'accounts-balance': number; + 'onbudget-accounts-balance': number; + 'offbudget-accounts-balance': number; + 'closed-accounts-balance': number; + balanceCleared: number; + balanceUncleared: number; + lastReconciled: string | null; + }; + category: { + // Common fields + 'uncategorized-amount': number; + 'uncategorized-balance': number; + + balance: number; + balanceCleared: number; + balanceUncleared: number; + }; + 'envelope-budget': { + // Common fields + 'uncategorized-amount': number; + 'uncategorized-balance': number; + + // Envelope budget fields + 'available-funds': number; + 'last-month-overspent': number; + buffered: number; + 'buffered-auto': number; + 'buffered-selected': number; + 'to-budget': number | null; + 'from-last-month': number; + 'total-budgeted': number; + 'total-income': number; + 'total-spent': number; + 'total-leftover': number; + 'group-sum-amount': number; + 'group-budget': number; + 'group-leftover': number; + budget: number; + 'sum-amount': number; + leftover: number; + carryover: number; + goal: number; + 'long-goal': number; + }; + 'tracking-budget': { + // Common fields + 'uncategorized-amount': number; + 'uncategorized-balance': number; + + // Tracking budget fields + 'total-budgeted': number; + 'total-budget-income': number; + 'total-saved': number; + 'total-income': number; + 'total-spent': number; + 'real-saved': number; + 'total-leftover': number; + 'group-sum-amount': number; + 'group-budget': number; + 'group-leftover': number; + budget: number; + 'sum-amount': number; + leftover: number; + carryover: number; + goal: number; + 'long-goal': number; + }; + [`balance`]: { + // Common fields + 'uncategorized-amount': number; + 'uncategorized-balance': number; + + // Balance fields + [key: `balance-query-${string}`]: number; + [key: `selected-transactions-${string}`]: Array<{ id: string }>; + [key: `selected-balance-${string}`]: number; + }; +}; + +export type SheetNames = keyof Spreadsheets & string; + +export type SheetFields = + keyof Spreadsheets[SheetName] & string; + +export type BindingObject< + SheetName extends SheetNames, + SheetFieldName extends SheetFields, +> = { + name: SheetFieldName; + value?: Spreadsheets[SheetName][SheetFieldName] | undefined; + query?: IQuery | undefined; +}; + +export type Binding< + SheetName extends SheetNames, + SheetFieldName extends SheetFields, +> = + | SheetFieldName + | { + name: SheetFieldName; + value?: Spreadsheets[SheetName][SheetFieldName] | undefined; + query?: IQuery | undefined; + }; + +export const parametrizedField = + () => + >(field: SheetFieldName) => + (id?: string): SheetFieldName => + `${field}-${id}` as SheetFieldName; diff --git a/packages/shared-types/src/util.ts b/packages/shared-types/src/util.ts new file mode 100644 index 0000000000..95f49cce42 --- /dev/null +++ b/packages/shared-types/src/util.ts @@ -0,0 +1,5 @@ +/** + * Utility types for plugins-core + */ + +export type WithRequired = T & Required>; diff --git a/packages/shared-types/tsconfig.json b/packages/shared-types/tsconfig.json new file mode 100644 index 0000000000..bd8058f60d --- /dev/null +++ b/packages/shared-types/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build", + "composite": true + }, + "include": ["src/**/*"] +} diff --git a/tsconfig.json b/tsconfig.json index 32f514cb1d..fba4023610 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,8 +32,24 @@ "paths": { // TEMPORARY: Until we can fix the "exports" in the loot-core package.json "loot-core/*": ["./packages/loot-core/src/*"], + "plugins-core/*": ["./packages/plugins-core/src/*"], + "@actual-app/plugins-core": ["./packages/plugins-core/src/index.ts"], + "@actual-app/plugins-core/server": [ + "./packages/plugins-core/src/server.ts" + ], + "@actual-app/plugins-core/client": [ + "./packages/plugins-core/src/client.ts" + ], + "@actual-app/plugins-core/*": ["./packages/plugins-core/src/*"], "@desktop-client/*": ["./packages/desktop-client/src/*"], - "@desktop-client/e2e/*": ["./packages/desktop-client/e2e/*"] + "@desktop-client/e2e/*": ["./packages/desktop-client/e2e/*"], + "@actual-app/query": ["./packages/query/src/index.ts"], + "@actual-app/query/*": ["./packages/query/src/*"], + "@actual-app/shared-types": ["./packages/shared-types/src/index.ts"], + "@actual-app/shared-types/*": ["./packages/shared-types/src/*"], + "@actual-app/shared-types/models": [ + "./packages/shared-types/src/models/index.ts" + ] }, "plugins": [ { @@ -53,7 +69,8 @@ "**/lib-dist/*", "**/test-results/*", "**/playwright-report/*", - "**/service-worker/*" + "**/service-worker/*", + "packages/test-plugin/*" ], "ts-node": { "compilerOptions": { diff --git a/upcoming-release-notes/5786.md b/upcoming-release-notes/5786.md new file mode 100644 index 0000000000..8d3d4e7788 --- /dev/null +++ b/upcoming-release-notes/5786.md @@ -0,0 +1,7 @@ +--- +category: Features +authors: [lelemm] +--- + +Add support for frontend plugins with a new core package for enhanced functionality. + diff --git a/yarn.lock b/yarn.lock index bc7f944218..6e4ef237fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,6 +79,43 @@ __metadata: languageName: unknown linkType: soft +"@actual-app/plugins-core@workspace:packages/plugins-core": + version: 0.0.0-use.local + resolution: "@actual-app/plugins-core@workspace:packages/plugins-core" + dependencies: + "@types/react": "npm:^19.1.4" + "@types/react-dom": "npm:^19.1.4" + typescript: "npm:^5.5.4" + typescript-eslint: "npm:^8.18.1" + typescript-strict-plugin: "npm:^2.4.4" + vite: "npm:^6.2.0" + vite-plugin-dts: "npm:^4.5.3" + peerDependencies: + i18next: ^25.2.1 + react: 19.1.0 + react-aria-components: ^1.7.1 + react-dom: 19.1.0 + react-i18next: ^16.0.0 + languageName: unknown + linkType: soft + +"@actual-app/query@workspace:packages/query": + version: 0.0.0-use.local + resolution: "@actual-app/query@workspace:packages/query" + dependencies: + "@actual-app/shared-types": "workspace:^" + typescript: "npm:^5.5.4" + languageName: unknown + linkType: soft + +"@actual-app/shared-types@workspace:^, @actual-app/shared-types@workspace:packages/shared-types": + version: 0.0.0-use.local + resolution: "@actual-app/shared-types@workspace:packages/shared-types" + dependencies: + typescript: "npm:^5.5.4" + languageName: unknown + linkType: soft + "@actual-app/sync-server@workspace:*, @actual-app/sync-server@workspace:packages/sync-server": version: 0.0.0-use.local resolution: "@actual-app/sync-server@workspace:packages/sync-server" @@ -5266,7 +5303,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.1": +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.1.4": version: 5.3.0 resolution: "@rollup/pluginutils@npm:5.3.0" dependencies: @@ -5454,6 +5491,77 @@ __metadata: languageName: node linkType: hard +"@rushstack/node-core-library@npm:5.17.0": + version: 5.17.0 + resolution: "@rushstack/node-core-library@npm:5.17.0" + dependencies: + ajv: "npm:~8.13.0" + ajv-draft-04: "npm:~1.0.0" + ajv-formats: "npm:~3.0.1" + fs-extra: "npm:~11.3.0" + import-lazy: "npm:~4.0.0" + jju: "npm:~1.4.0" + resolve: "npm:~1.22.1" + semver: "npm:~7.5.4" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/a4aae2502248ea7b150295cad83f0c3bba4281817f16dc80dc627452197be145d8548c7f57047d0ecfdf0a37b646e32fcea8fc2a3dc1a650c6bd95cb26f31794 + languageName: node + linkType: hard + +"@rushstack/problem-matcher@npm:0.1.1": + version: 0.1.1 + resolution: "@rushstack/problem-matcher@npm:0.1.1" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/a47c2d5fd0e3bbe7336f06c29ef91061e36ab8dafd04c861392806e60a3366fd8c3921be217adc71d039c8749f6b70a06c874ff314501eed5b7f8fb7b42c7a39 + languageName: node + linkType: hard + +"@rushstack/rig-package@npm:0.6.0": + version: 0.6.0 + resolution: "@rushstack/rig-package@npm:0.6.0" + dependencies: + resolve: "npm:~1.22.1" + strip-json-comments: "npm:~3.1.1" + checksum: 10/6ca5d6615365dfe4d78fdc52a1a145bec92bba79d8692db91d05c774b4ec4d9dc6c41b31949708d0312896b9c1c205a0f0eaa32f51ac7b1780415ac51c76af71 + languageName: node + linkType: hard + +"@rushstack/terminal@npm:0.19.1": + version: 0.19.1 + resolution: "@rushstack/terminal@npm:0.19.1" + dependencies: + "@rushstack/node-core-library": "npm:5.17.0" + "@rushstack/problem-matcher": "npm:0.1.1" + supports-color: "npm:~8.1.1" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/57f39076b0a8ffcb4631bb670ac7bbc748e3ecb339cdfcf3611ea51436b6235d0a6a7da0f457e0f7fd52787e71ff449d1026bbabc51d446c8e6f941eb0ba5572 + languageName: node + linkType: hard + +"@rushstack/ts-command-line@npm:5.1.1": + version: 5.1.1 + resolution: "@rushstack/ts-command-line@npm:5.1.1" + dependencies: + "@rushstack/terminal": "npm:0.19.1" + "@types/argparse": "npm:1.0.38" + argparse: "npm:~1.0.9" + string-argv: "npm:~0.3.1" + checksum: 10/53daad744574dec994be46ea7eba519753b4e05001f55d4cfb4715d5f821e830695ea04fe9bb4104dd411dd837e7e7f34706453cbc6ca0056acdcab314bc6fdc + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.34.0": version: 0.34.41 resolution: "@sinclair/typebox@npm:0.34.41" @@ -5945,6 +6053,13 @@ __metadata: languageName: node linkType: hard +"@types/argparse@npm:1.0.38": + version: 1.0.38 + resolution: "@types/argparse@npm:1.0.38" + checksum: 10/26ed7e3f1e3595efdb883a852f5205f971b798e4c28b7e30a32c5298eee596e8b45834ce831f014d250b9730819ab05acff5b31229666d3af4ba465b4697d0eb + languageName: node + linkType: hard + "@types/aria-query@npm:^5.0.1": version: 5.0.4 resolution: "@types/aria-query@npm:5.0.4" @@ -6508,6 +6623,15 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^19.1.4": + version: 19.1.13 + resolution: "@types/react@npm:19.1.13" + dependencies: + csstype: "npm:^3.0.2" + checksum: 10/a4e12df335ded76e931cc2ba2a4c8a61872ed840081eca83612fbdadc4afbf0cbd0ae31fdedc7fae7f0e02c90dac98dda517dfa73bec653dd4b1de2755431a62 + languageName: node + linkType: hard + "@types/resolve@npm:1.20.2": version: 1.20.2 resolution: "@types/resolve@npm:1.20.2" @@ -7202,6 +7326,94 @@ __metadata: languageName: node linkType: hard +"@volar/language-core@npm:2.4.23, @volar/language-core@npm:~2.4.11": + version: 2.4.23 + resolution: "@volar/language-core@npm:2.4.23" + dependencies: + "@volar/source-map": "npm:2.4.23" + checksum: 10/5c7a330be253580fff0ae4308fdd385680de3888b676fdc032bfc4a69f1014db5167e48347b2f85232503b09373deb495206322d665b995dcd99bd1da5eff2f1 + languageName: node + linkType: hard + +"@volar/source-map@npm:2.4.23": + version: 2.4.23 + resolution: "@volar/source-map@npm:2.4.23" + checksum: 10/511aeb03fb3715232e125cad24c011a11740b5572bac29fbf4ad34fe3374671178b67b7730ba3bb926568a715858fe433e1ff7661deeea4838095e4b4758613c + languageName: node + linkType: hard + +"@volar/typescript@npm:^2.4.11": + version: 2.4.23 + resolution: "@volar/typescript@npm:2.4.23" + dependencies: + "@volar/language-core": "npm:2.4.23" + path-browserify: "npm:^1.0.1" + vscode-uri: "npm:^3.0.8" + checksum: 10/8dcf522ea5e479a90b37389b852e38372080a2077d6e2ef8f6468bc640e89523dae99e5be85f579fe61ef2951a8d5bfeb656bb2e48ca1695dbd43e1e6749fb39 + languageName: node + linkType: hard + +"@vue/compiler-core@npm:3.5.21": + version: 3.5.21 + resolution: "@vue/compiler-core@npm:3.5.21" + dependencies: + "@babel/parser": "npm:^7.28.3" + "@vue/shared": "npm:3.5.21" + entities: "npm:^4.5.0" + estree-walker: "npm:^2.0.2" + source-map-js: "npm:^1.2.1" + checksum: 10/a50ccf92a9560490be40fed324b43d76c75d3df024fe4d52ac1630b1027b39035f9af8dca84f6c75e6063c0812fde8de36b43feb77b7af01a3ed7ca689f1680c + languageName: node + linkType: hard + +"@vue/compiler-dom@npm:^3.5.0": + version: 3.5.21 + resolution: "@vue/compiler-dom@npm:3.5.21" + dependencies: + "@vue/compiler-core": "npm:3.5.21" + "@vue/shared": "npm:3.5.21" + checksum: 10/371f8a88c1d3906feb39d8e06ccebc1a4459f1ebbb39f8b2081c424b6fc69a0f9f791a1e9a64ce825c54368bd0b6c518ab8a4d75a969ae3320824c68e747fdf7 + languageName: node + linkType: hard + +"@vue/compiler-vue2@npm:^2.7.16": + version: 2.7.16 + resolution: "@vue/compiler-vue2@npm:2.7.16" + dependencies: + de-indent: "npm:^1.0.2" + he: "npm:^1.2.0" + checksum: 10/739ad06be19206b2715707c226a070509bcf28c31b539a6fc932d220eb7b0c09109d71fded573ed0c4073429793a3513ca4a4e69ad4f7afc0c5bc3c28639e871 + languageName: node + linkType: hard + +"@vue/language-core@npm:2.2.0": + version: 2.2.0 + resolution: "@vue/language-core@npm:2.2.0" + dependencies: + "@volar/language-core": "npm:~2.4.11" + "@vue/compiler-dom": "npm:^3.5.0" + "@vue/compiler-vue2": "npm:^2.7.16" + "@vue/shared": "npm:^3.5.0" + alien-signals: "npm:^0.4.9" + minimatch: "npm:^9.0.3" + muggle-string: "npm:^0.4.1" + path-browserify: "npm:^1.0.1" + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/dc22d038509a58e5a5569fe19f67e7373067cde3531b1e405270dcbe026a8cdbb1de8a7a93d6aaa76a3c5b71393f5c6ec5d9125f0b050898cb96a5c62b4a8d88 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.21, @vue/shared@npm:^3.5.0": + version: 3.5.21 + resolution: "@vue/shared@npm:3.5.21" + checksum: 10/2bb8b884e88383d24ccbd31246d3079f53bc14ee3766c20364b3e2fff4f6d21d9900d8c88dc72acbad9f8a3981356297593b013562e51f3b85e78206e90a2e70 + languageName: node + linkType: hard + "@xmldom/xmldom@npm:^0.8.8": version: 0.8.11 resolution: "@xmldom/xmldom@npm:0.8.11" @@ -7361,6 +7573,32 @@ __metadata: languageName: node linkType: hard +"ajv-draft-04@npm:~1.0.0": + version: 1.0.0 + resolution: "ajv-draft-04@npm:1.0.0" + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10/3f11fa0e7f7359bef6608657f02ab78e9cc62b1fb7bdd860db0d00351b3863a1189c1a23b72466d2d82726cab4eb20725c76f5e7c134a89865e2bfd0e6828137 + languageName: node + linkType: hard + +"ajv-formats@npm:~3.0.1": + version: 3.0.1 + resolution: "ajv-formats@npm:3.0.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10/5679b9f9ced9d0213a202a37f3aa91efcffe59a6de1a6e3da5c873344d3c161820a1f11cc29899661fee36271fd2895dd3851b6461c902a752ad661d1c1e8722 + languageName: node + linkType: hard + "ajv-keywords@npm:^3.4.1": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" @@ -7382,7 +7620,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.6.0": +"ajv@npm:^8.0.0, ajv@npm:^8.6.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -7394,6 +7632,37 @@ __metadata: languageName: node linkType: hard +"ajv@npm:~8.12.0": + version: 8.12.0 + resolution: "ajv@npm:8.12.0" + dependencies: + fast-deep-equal: "npm:^3.1.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + uri-js: "npm:^4.2.2" + checksum: 10/b406f3b79b5756ac53bfe2c20852471b08e122bc1ee4cde08ae4d6a800574d9cd78d60c81c69c63ff81e4da7cd0b638fafbb2303ae580d49cf1600b9059efb85 + languageName: node + linkType: hard + +"ajv@npm:~8.13.0": + version: 8.13.0 + resolution: "ajv@npm:8.13.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + uri-js: "npm:^4.4.1" + checksum: 10/4ada268c9a6e44be87fd295df0f0a91267a7bae8dbc8a67a2d5799c3cb459232839c99d18b035597bb6e3ffe88af6979f7daece854f590a81ebbbc2dfa80002c + languageName: node + linkType: hard + +"alien-signals@npm:^0.4.9": + version: 0.4.14 + resolution: "alien-signals@npm:0.4.14" + checksum: 10/306a7f4a88a982d1619ff313ed078bdfe52bb9d1381590ef4c1c245812ce7274b9b645a6233214e764a1adbef21863bdf74d10aa4fb30917456b7dd7779df5fc + languageName: node + linkType: hard + "ansi-escapes@npm:^7.0.0": version: 7.1.1 resolution: "ansi-escapes@npm:7.1.1" @@ -7524,6 +7793,15 @@ __metadata: languageName: node linkType: hard +"argparse@npm:~1.0.9": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10/c6a621343a553ff3779390bb5ee9c2263d6643ebcd7843227bdde6cc7adbed796eb5540ca98db19e3fd7b4714e1faa51551f8849b268bb62df27ddb15cbcd91e + languageName: node + linkType: hard + "aria-hidden@npm:^1.2.4": version: 1.2.6 resolution: "aria-hidden@npm:1.2.6" @@ -8984,6 +9262,20 @@ __metadata: languageName: node linkType: hard +"confbox@npm:^0.1.8": + version: 0.1.8 + resolution: "confbox@npm:0.1.8" + checksum: 10/4ebcfb1c6a3b25276734ec5722e88768eb61fc02f98e11960b845c5c62bc27fd05f493d2a8244d9675b24ef95afe4c0d511cdcad02c72f5eeea463cc26687999 + languageName: node + linkType: hard + +"confbox@npm:^0.2.2": + version: 0.2.2 + resolution: "confbox@npm:0.2.2" + checksum: 10/988c7216f9b5aee5d8a8f32153a9164e1b58d92d8335c5daa323fd3fdee91f742ffc25f6c28b059474b6319204085eca985ab14c5a246988dc7ef1fe29414108 + languageName: node + linkType: hard + "config-file-ts@npm:0.2.8-rc1": version: 0.2.8-rc1 resolution: "config-file-ts@npm:0.2.8-rc1" @@ -9566,6 +9858,13 @@ __metadata: languageName: node linkType: hard +"de-indent@npm:^1.0.2": + version: 1.0.2 + resolution: "de-indent@npm:1.0.2" + checksum: 10/30bf43744dca005f9252dbb34ed95dcb3c30dfe52bfed84973b89c29eccff04e27769f222a34c61a93354acf47457785e9032e6184be390ed1d324fb9ab3f427 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -10222,7 +10521,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0, entities@npm:^4.4.0": +"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 @@ -11143,6 +11442,13 @@ __metadata: languageName: node linkType: hard +"exsolve@npm:^1.0.7": + version: 1.0.7 + resolution: "exsolve@npm:1.0.7" + checksum: 10/0c9fc0964da0154f38b55e612ed29bf5040f753d5d2db3a63559762237d0a86290e2f18997973343bb9900c07ab1e48596321de9d9d338e373b1f3f1a015e4c9 + languageName: node + linkType: hard + "extend@npm:^3.0.0": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -11277,7 +11583,7 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.5.0": +"fdir@npm:^6.4.4, fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" peerDependencies: @@ -11489,7 +11795,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0, fs-extra@npm:^11.3.2": +"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0, fs-extra@npm:^11.3.2, fs-extra@npm:~11.3.0": version: 11.3.2 resolution: "fs-extra@npm:11.3.2" dependencies: @@ -12165,6 +12471,15 @@ __metadata: languageName: node linkType: hard +"he@npm:^1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10/d09b2243da4e23f53336e8de3093e5c43d2c39f8d0d18817abfa32ce3e9355391b2edb4bb5edc376aea5d4b0b59d6a0482aab4c52bc02ef95751e4b818e847f1 + languageName: node + linkType: hard + "heimdalljs-logger@npm:^0.1.10, heimdalljs-logger@npm:^0.1.7": version: 0.1.10 resolution: "heimdalljs-logger@npm:0.1.10" @@ -12576,6 +12891,13 @@ __metadata: languageName: node linkType: hard +"import-lazy@npm:~4.0.0": + version: 4.0.0 + resolution: "import-lazy@npm:4.0.0" + checksum: 10/943309cc8eb01ada12700448c288b0384f77a1bc33c7e00fa4cb223c665f467a13ce9aaceb8d2e4cf586b07c1d2828040263dcc069873ce63cfc2ac6fd087971 + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -13340,6 +13662,13 @@ __metadata: languageName: node linkType: hard +"jju@npm:~1.4.0": + version: 1.4.0 + resolution: "jju@npm:1.4.0" + checksum: 10/1067ff8ce02221faac5a842116ed0ec79a53312a111d0bf8342a80bd02c0a3fdf0b8449694a65947db0a3e8420e8b326dffb489c7dd5866efc380c0d1708a707 + languageName: node + linkType: hard + "jose@npm:^4.15.9": version: 4.15.9 resolution: "jose@npm:4.15.9" @@ -13629,6 +13958,13 @@ __metadata: languageName: node linkType: hard +"kolorist@npm:^1.8.0": + version: 1.8.0 + resolution: "kolorist@npm:1.8.0" + checksum: 10/71d5d122951cc65f2f14c3e1d7f8fd91694b374647d4f6deec3816d018cd04a44edd9578d93e00c82c2053b925e5d30a0565746c4171f4ca9fce1a13bd5f3315 + languageName: node + linkType: hard + "kuler@npm:^2.0.0": version: 2.0.0 resolution: "kuler@npm:2.0.0" @@ -13763,6 +14099,17 @@ __metadata: languageName: node linkType: hard +"local-pkg@npm:^1.0.0": + version: 1.1.2 + resolution: "local-pkg@npm:1.1.2" + dependencies: + mlly: "npm:^1.7.4" + pkg-types: "npm:^2.3.0" + quansync: "npm:^0.2.11" + checksum: 10/761d82f40849e4721fa50d86782cf75bc2befb0696f32ac99869fb6f3033b904e4018f4bb8cdfde994d710816480dc1aba8e462c67ec20fe89d4700a245d17f8 + languageName: node + linkType: hard + "locate-path@npm:^6.0.0": version: 6.0.0 resolution: "locate-path@npm:6.0.0" @@ -13849,7 +14196,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21": +"lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 @@ -14938,7 +15285,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.0, minimatch@npm:^10.0.3": +"minimatch@npm:10.0.3, minimatch@npm:^10.0.0, minimatch@npm:^10.0.3": version: 10.0.3 resolution: "minimatch@npm:10.0.3" dependencies: @@ -15137,6 +15484,18 @@ __metadata: languageName: node linkType: hard +"mlly@npm:^1.7.4": + version: 1.8.0 + resolution: "mlly@npm:1.8.0" + dependencies: + acorn: "npm:^8.15.0" + pathe: "npm:^2.0.3" + pkg-types: "npm:^1.3.1" + ufo: "npm:^1.6.1" + checksum: 10/4db690a421076d5fe88331679f702b77a4bfc9fe3f324bc6150270fb0b69ecd4b5e43570b8e4573dde341515b3eac4daa720a6ac9f2715c210b670852641ab1c + languageName: node + linkType: hard + "mockdate@npm:^3.0.5": version: 3.0.5 resolution: "mockdate@npm:3.0.5" @@ -15158,6 +15517,13 @@ __metadata: languageName: node linkType: hard +"muggle-string@npm:^0.4.1": + version: 0.4.1 + resolution: "muggle-string@npm:0.4.1" + checksum: 10/8fa2ea08f497c04069718bd3fd1909b382114dacbad832d10967ca72690de43f5f8492d8ccfbf827d6be63868ed5fc10395e7b7c082aa95997eea498586c6620 + languageName: node + linkType: hard + "murmurhash@npm:^2.0.1": version: 2.0.1 resolution: "murmurhash@npm:2.0.1" @@ -16008,7 +16374,7 @@ __metadata: languageName: node linkType: hard -"pathe@npm:^2.0.3": +"pathe@npm:^2.0.1, pathe@npm:^2.0.3": version: 2.0.3 resolution: "pathe@npm:2.0.3" checksum: 10/01e9a69928f39087d96e1751ce7d6d50da8c39abf9a12e0ac2389c42c83bc76f78c45a475bd9026a02e6a6f79be63acc75667df855862fe567d99a00a540d23d @@ -16198,7 +16564,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.6": +"postcss@npm:^8.5.3, postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" dependencies: @@ -16468,6 +16834,13 @@ __metadata: languageName: node linkType: hard +"quansync@npm:^0.2.11": + version: 0.2.11 + resolution: "quansync@npm:0.2.11" + checksum: 10/d4f0cc21a25052a8a6183f17752a6221829c4795b40641de67c06945b356841ff00296d3700d0332dfe8e86100fdcc02f4be7559f3f1774a753b05adb7800d01 + languageName: node + linkType: hard + "querystring-es3@npm:^0.2.1": version: 0.2.1 resolution: "querystring-es3@npm:0.2.1" @@ -17437,7 +17810,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4": +"resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:~1.22.1, resolve@npm:~1.22.2": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -17463,7 +17836,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.10.1#optional!builtin, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.10.1#optional!builtin, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: @@ -17886,6 +18259,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:~7.5.4": + version: 7.5.4 + resolution: "semver@npm:7.5.4" + dependencies: + lru-cache: "npm:^6.0.0" + bin: + semver: bin/semver.js + checksum: 10/985dec0d372370229a262c737063860fabd4a1c730662c1ea3200a2f649117761a42184c96df62a0e885e76fbd5dace41087d6c1ac0351b13c0df5d6bcb1b5ac + languageName: node + linkType: hard + "send@npm:^1.1.0, send@npm:^1.2.0": version: 1.2.0 resolution: "send@npm:1.2.0" @@ -18288,7 +18672,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1": +"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff @@ -18366,6 +18750,13 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10/c34828732ab8509c2741e5fd1af6b767c3daf2c642f267788f933a65b1614943c282e74c4284f4fa749c264b18ee016a0d37a3e5b73aee446da46277d3a85daa + languageName: node + linkType: hard + "ssri@npm:^12.0.0": version: 12.0.0 resolution: "ssri@npm:12.0.0" @@ -18485,7 +18876,7 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:^0.3.2": +"string-argv@npm:^0.3.2, string-argv@npm:~0.3.1": version: 0.3.2 resolution: "string-argv@npm:0.3.2" checksum: 10/f9d3addf887026b4b5f997a271149e93bf71efc8692e7dc0816e8807f960b18bcb9787b45beedf0f97ff459575ee389af3f189d8b649834cac602f2e857e75af @@ -18721,7 +19112,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:^3.1.1, strip-json-comments@npm:~3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 10/492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 @@ -18830,6 +19221,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:~8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -19081,7 +19481,7 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15": +"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" dependencies: @@ -19480,7 +19880,7 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:^8.46.0": +"typescript-eslint@npm:^8.18.1, typescript-eslint@npm:^8.46.0": version: 8.46.0 resolution: "typescript-eslint@npm:8.46.0" dependencies: @@ -19511,7 +19911,17 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.0.4, typescript@npm:^5.4.3, typescript@npm:^5.9.3": +"typescript@npm:5.8.2": + version: 5.8.2 + resolution: "typescript@npm:5.8.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/dbc2168a55d56771f4d581997be52bab5cbc09734fec976cfbaabd787e61fb4c6cf9125fd48c6f98054ce549c77ecedefc7f64252a830dd8e9c3381f61fbeb78 + languageName: node + linkType: hard + +"typescript@npm:^5.0.4, typescript@npm:^5.4.3, typescript@npm:^5.5.4, typescript@npm:^5.9.3": version: 5.9.3 resolution: "typescript@npm:5.9.3" bin: @@ -19521,7 +19931,17 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.4.3#optional!builtin, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": +"typescript@patch:typescript@npm%3A5.8.2#optional!builtin": + version: 5.8.2 + resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/97920a082ffc57583b1cb6bc4faa502acc156358e03f54c7fc7fdf0b61c439a717f4c9070c449ee9ee683d4cfc3bb203127c2b9794b2950f66d9d307a4ff262c + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.4.3#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": version: 5.9.3 resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: @@ -19558,6 +19978,13 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.6.1": + version: 1.6.1 + resolution: "ufo@npm:1.6.1" + checksum: 10/088a68133b93af183b093e5a8730a40fe7fd675d3dc0656ea7512f180af45c92300c294f14d4d46d4b2b553e3e52d3b13d4856b9885e620e7001edf85531234e + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.19.3 resolution: "uglify-js@npm:3.19.3" @@ -19888,7 +20315,7 @@ __metadata: languageName: node linkType: hard -"uri-js@npm:^4.2.2": +"uri-js@npm:^4.2.2, uri-js@npm:^4.4.1": version: 4.4.1 resolution: "uri-js@npm:4.4.1" dependencies: @@ -20160,6 +20587,29 @@ __metadata: languageName: node linkType: hard +"vite-plugin-dts@npm:^4.5.3": + version: 4.5.4 + resolution: "vite-plugin-dts@npm:4.5.4" + dependencies: + "@microsoft/api-extractor": "npm:^7.50.1" + "@rollup/pluginutils": "npm:^5.1.4" + "@volar/typescript": "npm:^2.4.11" + "@vue/language-core": "npm:2.2.0" + compare-versions: "npm:^6.1.1" + debug: "npm:^4.4.0" + kolorist: "npm:^1.8.0" + local-pkg: "npm:^1.0.0" + magic-string: "npm:^0.30.17" + peerDependencies: + typescript: "*" + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: 10/1504a8da02f8015c3138ef57a690179ad9b9d4369a054ae48b52a1c8cbc7ad05474551eab309cd90cf758800e17b67ccd13317aa5f8e28efde957547c7a339fb + languageName: node + linkType: hard + "vite-plugin-node-polyfills@npm:^0.24.0": version: 0.24.0 resolution: "vite-plugin-node-polyfills@npm:0.24.0" @@ -20273,6 +20723,61 @@ __metadata: languageName: node linkType: hard +"vite@npm:^6.2.0": + version: 6.3.6 + resolution: "vite@npm:6.3.6" + dependencies: + esbuild: "npm:^0.25.0" + fdir: "npm:^6.4.4" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.2" + postcss: "npm:^8.5.3" + rollup: "npm:^4.34.9" + tinyglobby: "npm:^0.2.13" + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10/8b8b6fe12318ca457396bf2053df7056cf4810f1d4a43b36b6afe59860e32b749c0685a290fe8a973b0d3da179ceec4c30cebbd3c91d0c47fbcf6436b17bdeef + languageName: node + linkType: hard + "vitest@npm:^3.2.4": version: 3.2.4 resolution: "vitest@npm:3.2.4"