diff --git a/.eslintignore b/.eslintignore index f5f8a9e7ccd..2487b54410d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,6 @@ packages/api/app/bundle.api.js packages/api/dist +packages/api/@types packages/api/migrations packages/crdt/dist diff --git a/.eslintrc.js b/.eslintrc.js index c411995fc87..bf7bb15cb14 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -38,6 +38,7 @@ module.exports = { extends: [ 'react-app', 'plugin:react/recommended', + 'plugin:prettier/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:import/typescript', ], @@ -46,6 +47,7 @@ module.exports = { reportUnusedDisableDirectives: true, globals: { globalThis: false, + vi: true, }, rules: { 'prettier/prettier': 'warn', @@ -163,6 +165,11 @@ module.exports = { { patterns: [...restrictedImportPatterns, ...restrictedImportColors] }, ], + '@typescript-eslint/ban-ts-comment': [ + 'error', + { 'ts-ignore': 'allow-with-description' }, + ], + // Rules disable during TS migration '@typescript-eslint/no-var-requires': 'off', 'prefer-const': 'warn', diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 254c18462bc..85235450ebf 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -18,6 +18,18 @@ body: required: true validations: required: true + - type: checkboxes + id: bank-sync-issue + attributes: + label: 'Is this related to GoCardless, Simplefin or another bank-sync provider?' + description: 'Most issues with bank-sync providers are due to a lack of a custom bank-mapper (i.e. payee or other fields not coming through). In such cases you can create a custom bank mapper in [actual-server](https://github.com/actualbudget/actual-server/blob/master/src/app-gocardless/README.md) repository. Other likely issue is misconfigured server - in which case please reach out via the [community Discord](https://discord.gg/pRYNYr4W5A) to get support.' + options: + - label: 'I have checked my server logs and could not see any errors there' + - label: 'I will be attaching my server logs to this issue' + - label: 'I will be attaching my client-side (browser) logs to this issue' + - label: 'I understand that this issue will be automatically closed if insufficient information is provided' + validations: + required: false - type: textarea id: what-happened attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0cec6..150ae8082b4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,5 @@ blank_issues_enabled: false +contact_links: + - name: Support + url: https://discord.gg/pRYNYr4W5A + about: Need help with something? Perhaps having issues setting up bank-sync with GoCardless or SimpleFin? Reach out to the community on Discord. diff --git a/.gitignore b/.gitignore index 691472708e3..9777ed2d001 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ !data/.gitkeep /data2 packages/api/dist +packages/api/@types packages/crdt/dist packages/desktop-electron/client-build packages/desktop-electron/.electron-symbols diff --git a/package.json b/package.json index db6163d7b54..62c6175f39c 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "vrt": "yarn workspaces foreach --all --parallel --verbose run vrt", "rebuild-electron": "./node_modules/.bin/electron-rebuild -f -m ./packages/loot-core", "rebuild-node": "yarn workspace loot-core rebuild", - "lint": "eslint . --max-warnings 0", + "lint": "eslint . --max-warnings 0 --ext .js,.jsx,.ts,.tsx", "lint:verbose": "DEBUG=eslint:cli-engine eslint . --max-warnings 0", "typecheck": "yarn tsc && tsc-strict", "jq": "./node_modules/node-jq/bin/jq" @@ -44,15 +44,16 @@ "devDependencies": { "cross-env": "^7.0.3", "eslint": "^8.37.0", + "eslint-config-prettier": "^9.1.0", "eslint-config-react-app": "7.0.1", "eslint-import-resolver-typescript": "3.5.5", "eslint-plugin-import": "2.27.5", - "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-prettier": "5.1.3", "eslint-plugin-react": "7.32.2", "eslint-plugin-rulesdir": "^0.2.2", "node-jq": "^4.0.1", "npm-run-all": "^4.1.3", - "prettier": "2.8.2", + "prettier": "3.2.4", "react-refresh": "^0.14.0", "source-map-support": "^0.5.21", "typescript": "^5.0.2", diff --git a/packages/api/index.js b/packages/api/index.js deleted file mode 100644 index 4473e540c86..00000000000 --- a/packages/api/index.js +++ /dev/null @@ -1,38 +0,0 @@ -// eslint-disable-next-line import/extensions -import * as bundle from './app/bundle.api.js'; -import * as injected from './injected'; -import { validateNodeVersion } from './validateNodeVersion'; - -let actualApp; -export const internal = bundle.lib; - -// DEPRECATED: remove the next line in @actual-app/api v7 -export * as methods from './methods'; - -export * from './methods'; -export * as utils from './utils'; - -export async function init(config = {}) { - if (actualApp) { - return; - } - - validateNodeVersion(); - - global.fetch = (...args) => - import('node-fetch').then(({ default: fetch }) => fetch(...args)); - - await bundle.init(config); - actualApp = bundle.lib; - - injected.override(bundle.lib.send); - return bundle.lib; -} - -export async function shutdown() { - if (actualApp) { - await actualApp.send('sync'); - await actualApp.send('close-budget'); - actualApp = null; - } -} diff --git a/packages/api/index.ts b/packages/api/index.ts new file mode 100644 index 00000000000..be3f92d8bff --- /dev/null +++ b/packages/api/index.ts @@ -0,0 +1,53 @@ +import type { + RequestInfo as FetchInfo, + RequestInit as FetchInit, + // @ts-ignore: false-positive commonjs module error on build until typescript 5.3 +} from 'node-fetch'; // with { 'resolution-mode': 'import' }; + +// loot-core types +import type { InitConfig } from 'loot-core/server/main'; + +// @ts-ignore: bundle not available until we build it +// eslint-disable-next-line import/extensions +import * as bundle from './app/bundle.api.js'; +import * as injected from './injected'; +import { validateNodeVersion } from './validateNodeVersion'; + +let actualApp: null | typeof bundle.lib; +export const internal = bundle.lib; + +// DEPRECATED: remove the next line in @actual-app/api v7 +export * as methods from './methods'; + +export * from './methods'; +export * as utils from './utils'; + +export async function init(config: InitConfig = {}) { + if (actualApp) { + return; + } + + validateNodeVersion(); + + if (!globalThis.fetch) { + globalThis.fetch = (url: URL | RequestInfo, init?: RequestInit) => { + return import('node-fetch').then(({ default: fetch }) => + fetch(url as unknown as FetchInfo, init as unknown as FetchInit), + ) as unknown as Promise; + }; + } + + await bundle.init(config); + actualApp = bundle.lib; + + injected.override(bundle.lib.send); + return bundle.lib; +} + +export async function shutdown() { + if (actualApp) { + await actualApp.send('sync'); + await actualApp.send('close-budget'); + actualApp = null; + } +} diff --git a/packages/api/methods.js b/packages/api/methods.ts similarity index 86% rename from packages/api/methods.js rename to packages/api/methods.ts index 9af541f2670..03acaf0f4f6 100644 --- a/packages/api/methods.js +++ b/packages/api/methods.ts @@ -1,8 +1,14 @@ +// @ts-strict-ignore +import type { Handlers } from 'loot-core/src/types/handlers'; + import * as injected from './injected'; export { q } from './app/query'; -function send(name, args) { +function send( + name: K, + args?: Parameters[0], +): Promise>> { return injected.send(name, args); } @@ -21,7 +27,7 @@ export async function loadBudget(budgetId) { return send('api/load-budget', { id: budgetId }); } -export async function downloadBudget(syncId, { password } = {}) { +export async function downloadBudget(syncId, { password }: { password? } = {}) { return send('api/download-budget', { syncId, password }); } @@ -79,10 +85,6 @@ export function getTransactions(accountId, startDate, endDate) { return send('api/transactions-get', { accountId, startDate, endDate }); } -export function filterTransactions(accountId, text) { - return send('api/transactions-filter', { accountId, text }); -} - export function updateTransaction(id, fields) { return send('api/transaction-update', { id, fields }); } @@ -95,7 +97,7 @@ export function getAccounts() { return send('api/accounts-get'); } -export function createAccount(account, initialBalance) { +export function createAccount(account, initialBalance?) { return send('api/account-create', { account, initialBalance }); } @@ -103,7 +105,7 @@ export function updateAccount(id, fields) { return send('api/account-update', { id, fields }); } -export function closeAccount(id, transferAccountId, transferCategoryId) { +export function closeAccount(id, transferAccountId?, transferCategoryId?) { return send('api/account-close', { id, transferAccountId, @@ -127,7 +129,7 @@ export function updateCategoryGroup(id, fields) { return send('api/category-group-update', { id, fields }); } -export function deleteCategoryGroup(id, transferCategoryId) { +export function deleteCategoryGroup(id, transferCategoryId?) { return send('api/category-group-delete', { id, transferCategoryId }); } @@ -143,7 +145,7 @@ export function updateCategory(id, fields) { return send('api/category-update', { id, fields }); } -export function deleteCategory(id, transferCategoryId) { +export function deleteCategory(id, transferCategoryId?) { return send('api/category-delete', { id, transferCategoryId }); } diff --git a/packages/api/package.json b/packages/api/package.json index 7c2d9a01e5b..b0433a638e8 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -7,17 +7,18 @@ "node": ">=18.12.0" }, "main": "dist/index.js", - "types": "dist/index.d.ts", + "types": "@types/index.d.ts", "files": [ "dist" ], "scripts": { "build:app": "yarn workspace loot-core build:api", - "build:node": "tsc --p tsconfig.dist.json", + "build:node": "tsc --p tsconfig.dist.json && tsc-alias -p tsconfig.dist.json", "build:migrations": "cp migrations/*.sql dist/migrations", "build:default-db": "cp default-db.sqlite dist/", - "build": "rm -rf dist && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db", - "test": "yarn run build:app && jest -c jest.config.js" + "build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db", + "test": "yarn run build:app && jest -c jest.config.js", + "clean": "rm -rf dist @types" }, "dependencies": { "better-sqlite3": "^9.2.2", @@ -31,6 +32,7 @@ "@types/jest": "^27.5.0", "@types/uuid": "^9.0.2", "jest": "^27.0.0", + "tsc-alias": "^1.8.8", "typescript": "^5.0.2" } } diff --git a/packages/api/tsconfig.dist.json b/packages/api/tsconfig.dist.json index 6704cb1fb7c..12caac8f770 100644 --- a/packages/api/tsconfig.dist.json +++ b/packages/api/tsconfig.dist.json @@ -8,8 +8,12 @@ "moduleResolution": "Node16", "noEmit": false, "declaration": true, - "outDir": "dist" + "outDir": "dist", + "declarationDir": "@types", + "paths": { + "loot-core/*": ["./@types/loot-core/*"], + } }, "include": ["."], - "exclude": ["dist"] + "exclude": ["**/node_modules/*", "dist", "@types"] } diff --git a/packages/crdt/src/crdt/timestamp.ts b/packages/crdt/src/crdt/timestamp.ts index 4eb8f399c21..ab54cf707dd 100644 --- a/packages/crdt/src/crdt/timestamp.ts +++ b/packages/crdt/src/crdt/timestamp.ts @@ -267,10 +267,10 @@ export class Timestamp { lNew === lOld && lNew === lMsg ? Math.max(cOld, cMsg) + 1 : lNew === lOld - ? cOld + 1 - : lNew === lMsg - ? cMsg + 1 - : 0; + ? cOld + 1 + : lNew === lMsg + ? cMsg + 1 + : 0; // check the result for drift and counter overflow if (lNew - phys > config.maxDrift) { diff --git a/packages/desktop-client/src/components/Notifications.tsx b/packages/desktop-client/src/components/Notifications.tsx index 122a1231870..d134ed26148 100644 --- a/packages/desktop-client/src/components/Notifications.tsx +++ b/packages/desktop-client/src/components/Notifications.tsx @@ -119,8 +119,8 @@ function Notification({ color: positive ? theme.noticeText : error - ? theme.errorTextDark - : theme.warningTextDark, + ? theme.errorTextDark + : theme.warningTextDark, }} > diff --git a/packages/desktop-client/src/components/Titlebar.tsx b/packages/desktop-client/src/components/Titlebar.tsx index e3487eddac9..57e16344bb8 100644 --- a/packages/desktop-client/src/components/Titlebar.tsx +++ b/packages/desktop-client/src/components/Titlebar.tsx @@ -177,18 +177,18 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) { syncState === 'error' ? theme.errorText : syncState === 'disabled' || - syncState === 'offline' || - syncState === 'local' - ? theme.mobileHeaderTextSubdued - : theme.mobileHeaderText; + syncState === 'offline' || + syncState === 'local' + ? theme.mobileHeaderTextSubdued + : theme.mobileHeaderText; const desktopColor = syncState === 'error' ? theme.errorTextDark : syncState === 'disabled' || - syncState === 'offline' || - syncState === 'local' - ? theme.tableTextLight - : 'inherit'; + syncState === 'offline' || + syncState === 'local' + ? theme.tableTextLight + : 'inherit'; const activeStyle = isMobile ? { @@ -263,8 +263,8 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) { {syncState === 'disabled' ? 'Disabled' : syncState === 'offline' - ? 'Offline' - : 'Sync'} + ? 'Offline' + : 'Sync'} diff --git a/packages/desktop-client/src/components/accounts/Balance.jsx b/packages/desktop-client/src/components/accounts/Balance.jsx index f2cb12c752f..d1df3c59a3b 100644 --- a/packages/desktop-client/src/components/accounts/Balance.jsx +++ b/packages/desktop-client/src/components/accounts/Balance.jsx @@ -159,8 +159,8 @@ export function Balances({ value < 0 ? theme.errorText : value > 0 - ? theme.noticeTextLight - : theme.pageTextSubdued, + ? theme.noticeTextLight + : theme.pageTextSubdued, })} privacyFilter={{ blurIntensity: 5, diff --git a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx index 3b514ac2f01..f296e7b297f 100644 --- a/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/AccountAutocomplete.tsx @@ -41,8 +41,8 @@ function AccountList({ item.closed ? 'Closed Accounts' : item.offbudget - ? 'Off Budget' - : 'For Budget' + ? 'Off Budget' + : 'For Budget' }`; lastItem = item; diff --git a/packages/desktop-client/src/components/budget/BudgetCategories.jsx b/packages/desktop-client/src/components/budget/BudgetCategories.jsx index a223fea97e7..c80bfb2ab35 100644 --- a/packages/desktop-client/src/components/budget/BudgetCategories.jsx +++ b/packages/desktop-client/src/components/budget/BudgetCategories.jsx @@ -292,8 +292,8 @@ export const BudgetCategories = memo( item.value ? item.value.id : item.type === 'income-separator' - ? 'separator' - : idx + ? 'separator' + : idx } value={pos} > diff --git a/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx b/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx index fb5b9d5c24d..3c2251d77b3 100644 --- a/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx +++ b/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx @@ -117,8 +117,8 @@ function Saved({ projected, onClick }) { color: projected ? theme.warningText : isNegative - ? theme.errorTextDark - : theme.formInputText, + ? theme.errorTextDark + : theme.formInputText, }} /> diff --git a/packages/desktop-client/src/components/budget/report/budgetsummary/Saved.tsx b/packages/desktop-client/src/components/budget/report/budgetsummary/Saved.tsx index 3575bd63f07..b17906ec4d9 100644 --- a/packages/desktop-client/src/components/budget/report/budgetsummary/Saved.tsx +++ b/packages/desktop-client/src/components/budget/report/budgetsummary/Saved.tsx @@ -81,8 +81,8 @@ export function Saved({ projected, style }: SavedProps) { color: projected ? theme.warningText : isNegative - ? theme.errorTextDark - : theme.upcomingText, + ? theme.errorTextDark + : theme.upcomingText, }, ])}`} > diff --git a/packages/desktop-client/src/components/budget/util.ts b/packages/desktop-client/src/components/budget/util.ts index ed0f93bb953..cb7b926b734 100644 --- a/packages/desktop-client/src/components/budget/util.ts +++ b/packages/desktop-client/src/components/budget/util.ts @@ -73,8 +73,8 @@ export function makeAmountFullStyle(value: number) { value < 0 ? theme.errorText : value === 0 - ? theme.tableTextSubdued - : theme.noticeText, + ? theme.tableTextSubdued + : theme.noticeText, }; } diff --git a/packages/desktop-client/src/components/common/AnchorLink.tsx b/packages/desktop-client/src/components/common/AnchorLink.tsx index 5defecba686..ed596b00d28 100644 --- a/packages/desktop-client/src/components/common/AnchorLink.tsx +++ b/packages/desktop-client/src/components/common/AnchorLink.tsx @@ -3,6 +3,8 @@ import { NavLink, useMatch } from 'react-router-dom'; import { css } from 'glamor'; +import { type CustomReportEntity } from 'loot-core/src/types/models'; + import { type CSSProperties, styles } from '../../style'; type AnchorLinkProps = { @@ -10,6 +12,7 @@ type AnchorLinkProps = { style?: CSSProperties; activeStyle?: CSSProperties; children?: ReactNode; + report?: CustomReportEntity; }; export function AnchorLink({ @@ -17,12 +20,14 @@ export function AnchorLink({ style, activeStyle, children, + report, }: AnchorLinkProps) { const match = useMatch({ path: to }); return ( void; + style?: CSSProperties; +}) { return ( -