diff --git a/.eslintrc.js b/.eslintrc.js index 0c2caca16c2..c411995fc87 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,6 +44,9 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: [path.join(__dirname, './tsconfig.json')] }, reportUnusedDisableDirectives: true, + globals: { + globalThis: false, + }, rules: { 'prettier/prettier': 'warn', @@ -165,6 +168,7 @@ module.exports = { 'prefer-const': 'warn', 'prefer-spread': 'off', '@typescript-eslint/no-empty-function': 'off', + 'import/no-default-export': 'warn', }, overrides: [ { @@ -200,6 +204,26 @@ module.exports = { ], }, }, + { + files: ['./packages/desktop-client/**/*'], + excludedFiles: [ + './packages/desktop-client/src/hooks/useNavigate.{ts,tsx}', + ], + rules: { + 'no-restricted-imports': [ + 'warn', + { + patterns: [ + { + group: ['react-router-dom'], + importNames: ['useNavigate'], + message: 'Please use Actual’s useNavigate() hook instead.', + }, + ], + }, + ], + }, + }, { files: ['./packages/loot-core/src/**/*'], rules: { @@ -238,6 +262,15 @@ module.exports = { 'no-restricted-imports': ['off', { patterns: restrictedImportColors }], }, }, + { + files: [ + './packages/api/migrations/*', + './packages/loot-core/migrations/*', + ], + rules: { + 'import/no-default-export': 'off', + }, + }, ], settings: { 'import/resolver': { diff --git a/.gitattributes b/.gitattributes index 21f3d101a30..a592bc40b1e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,4 +16,4 @@ yarn.lock text eol=lf # Denote all files that are truly binary and should not be modified. *.png binary -*.jpg binary \ No newline at end of file +*.jpg binary diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 3276e403f1a..1d48d375a87 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -66,38 +66,3 @@ jobs: name: desktop-client-test-results path: packages/desktop-client/test-results/ retention-days: 30 - - uses: actions-ecosystem/action-add-labels@v1 - if: failure() - with: - labels: ':red_circle: VRT failing' - - uses: actions-ecosystem/action-remove-labels@v1 - if: success() - with: - labels: ':red_circle: VRT failing' - - name: Find Comment - uses: peter-evans/find-comment@v2 - id: fc - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: VRT - - name: Create comment if failed - if: failure() - uses: peter-evans/create-or-update-comment@v3 - with: - comment-id: ${{ steps.fc.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - body: | - :wave: Looks like VRT (visual regression tests) are failing in this PR. This indicates a problem in the app. It could be either a bug in this PR or a visual change introduced by changing something. - - To fix this: please follow [these instructions](https://github.com/actualbudget/actual/blob/master/packages/desktop-client/README.md#visual-regression) and review the output of the failing CI job to see the generated screenshots. - - We look forward to reviewing this PR once all the CI jobs have passed successfully! - edit-mode: replace - - name: Update comment when CI job passes - if: success() && steps.fc.outputs.comment-id != '' - uses: peter-evans/create-or-update-comment@v3 - with: - comment-id: ${{ steps.fc.outputs.comment-id }} - body: The VRT tests have passed! Thank you! - edit-mode: replace diff --git a/.github/workflows/electron-master.yml b/.github/workflows/electron-master.yml new file mode 100644 index 00000000000..0fe0dd17025 --- /dev/null +++ b/.github/workflows/electron-master.yml @@ -0,0 +1,50 @@ +name: Electron + +defaults: + run: + shell: bash + +env: + CI: true + +on: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - if: ${{ startsWith(matrix.os, 'windows') }} + run: pip.exe install setuptools + - if: ${{ ! startsWith(matrix.os, 'windows') }} + run: python3 -m pip install setuptools + - name: Set up environment + uses: ./.github/actions/setup + - name: Build Electron + run: ./bin/package-electron + env: + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + CSC_LINK: ${{ secrets.CSC_LINK }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + - name: Upload Build + uses: actions/upload-artifact@v3 + with: + name: actual-electron-${{ matrix.os }} + path: | + packages/desktop-electron/dist/*.dmg + packages/desktop-electron/dist/*.exe + packages/desktop-electron/dist/*.AppImage diff --git a/.github/workflows/electron.yml b/.github/workflows/electron-pr.yml similarity index 89% rename from .github/workflows/electron.yml rename to .github/workflows/electron-pr.yml index 36d510215f4..8a5f2a98682 100644 --- a/.github/workflows/electron.yml +++ b/.github/workflows/electron-pr.yml @@ -8,14 +8,11 @@ env: CI: true on: - push: - branches: - - master pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: build: diff --git a/.github/workflows/size-compare.yml b/.github/workflows/size-compare.yml index 0dfcda68a63..40da2aad617 100644 --- a/.github/workflows/size-compare.yml +++ b/.github/workflows/size-compare.yml @@ -66,11 +66,11 @@ jobs: run: | sed -i -E 's/\.[0-9a-f]{8,}\././g' ./head/*.json sed -i -E 's/\.[0-9a-f]{8,}\././g' ./base/*.json - - uses: github/webpack-bundlesize-compare-action@v1.8.2 + - uses: twk3/rollup-size-compare-action@v1.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} - current-stats-json-path: ./head/desktop-client-stats.json - base-stats-json-path: ./base/desktop-client-stats.json + current-stats-json-path: ./head/web-stats.json + base-stats-json-path: ./base/web-stats.json title: desktop-client - uses: github/webpack-bundlesize-compare-action@v1.8.2 diff --git a/.secret-tokens.example b/.secret-tokens.example new file mode 100644 index 00000000000..1675ffbfd99 --- /dev/null +++ b/.secret-tokens.example @@ -0,0 +1,2 @@ +export APPLE_ID=example@email.com +export APPLE_APP_SPECIFIC_PASSWORD=password diff --git a/bin/package-electron b/bin/package-electron index 0caece7e196..801a6c5e2fe 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -3,7 +3,6 @@ ROOT=`dirname $0` RELEASE="" -RELEASE_NOTES="" # TODO: figure out automation for release notes when we start publishing electron versions CI=${CI:-false} cd "$ROOT/.." @@ -47,15 +46,13 @@ yarn workspace desktop-electron update-client cd packages/desktop-electron; yarn clean; - export npm_config_better_sqlite3_binary_host="https://static.actualbudget.com/prebuild/better-sqlite3" - if [ "$RELEASE" == "production" ]; then if [ -f ../../.secret-tokens ]; then source ../../.secret-tokens fi - yarn build --publish always -c.releaseInfo.releaseNotes="$RELEASE_NOTES" --arm64 --x64 + yarn build --publish never --arm64 --x64 - echo "\nCreated release with release notes \"$RELEASE_NOTES\"" + echo "\nCreated release" else SKIP_NOTARIZATION=true yarn build --publish never --x64 fi diff --git a/packages/api/app/query.js b/packages/api/app/query.js index dddb807de3e..f9f8079951e 100644 --- a/packages/api/app/query.js +++ b/packages/api/app/query.js @@ -99,6 +99,6 @@ class Query { } } -export default function q(table) { +export function q(table) { return new Query({ table }); } diff --git a/packages/api/methods.js b/packages/api/methods.js index 7d6ef8a29f2..9af541f2670 100644 --- a/packages/api/methods.js +++ b/packages/api/methods.js @@ -1,6 +1,6 @@ import * as injected from './injected'; -export { default as q } from './app/query'; +export { q } from './app/query'; function send(name, args) { return injected.send(name, args); diff --git a/packages/api/methods.test.ts b/packages/api/methods.test.ts index cca376242fc..f38a6985e70 100644 --- a/packages/api/methods.test.ts +++ b/packages/api/methods.test.ts @@ -272,4 +272,117 @@ describe('API CRUD operations', () => { ]), ); }); + + // apis: createPayee, getPayees, updatePayee, deletePayee + test('Payees: successfully update payees', async () => { + const payeeId1 = await api.createPayee({ name: 'test-payee1' }); + const payeeId2 = await api.createPayee({ name: 'test-payee2' }); + let payees = await api.getPayees(); + + // payees successfully created + expect(payees).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: payeeId1, + name: 'test-payee1', + }), + expect.objectContaining({ + id: payeeId2, + name: 'test-payee2', + }), + ]), + ); + + await api.updatePayee(payeeId1, { name: 'test-updated-payee' }); + await api.deletePayee(payeeId2); + + // confirm update and delete were successful + payees = await api.getPayees(); + expect(payees).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: payeeId1, + name: 'test-updated-payee', + }), + expect.not.objectContaining({ + name: 'test-payee1', + }), + expect.not.objectContaining({ + id: payeeId2, + }), + ]), + ); + }); + + // apis: addTransactions, getTransactions, importTransactions, updateTransaction, deleteTransaction + test('Transactions: successfully update transactions', async () => { + const accountId = await api.createAccount({ name: 'test-account' }, 0); + + let newTransaction = [ + { date: '2023-11-03', imported_id: '11', amount: 100 }, + { date: '2023-11-03', imported_id: '11', amount: 100 }, + ]; + + const addResult = await api.addTransactions(accountId, newTransaction, { + learnCategories: true, + runTransfers: true, + }); + expect(addResult).toBe('ok'); + + // confirm added transactions exist + let transactions = await api.getTransactions( + accountId, + '2023-11-01', + '2023-11-30', + ); + expect(transactions).toEqual( + expect.arrayContaining( + newTransaction.map(trans => expect.objectContaining(trans)), + ), + ); + expect(transactions).toHaveLength(2); + + newTransaction = [ + { date: '2023-12-03', imported_id: '11', amount: 100 }, + { date: '2023-12-03', imported_id: '22', amount: 200 }, + ]; + + const reconciled = await api.importTransactions(accountId, newTransaction); + + // Expect it to reconcile and to have updated one of the previous transactions + expect(reconciled.added).toHaveLength(1); + expect(reconciled.updated).toHaveLength(1); + + // confirm imported transactions exist + transactions = await api.getTransactions( + accountId, + '2023-12-01', + '2023-12-31', + ); + expect(transactions).toEqual( + expect.arrayContaining( + newTransaction.map(trans => expect.objectContaining(trans)), + ), + ); + expect(transactions).toHaveLength(2); + + const idToUpdate = reconciled.added[0]; + const idToDelete = reconciled.updated[0]; + await api.updateTransaction(idToUpdate, { amount: 500 }); + await api.deleteTransaction(idToDelete); + + // confirm updates and deletions work + transactions = await api.getTransactions( + accountId, + '2023-12-01', + '2023-12-31', + ); + expect(transactions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: idToUpdate, amount: 500 }), + expect.not.objectContaining({ id: idToDelete }), + ]), + ); + expect(transactions).toHaveLength(1); + }); }); diff --git a/packages/api/package.json b/packages/api/package.json index e3c71255ff5..7c2d9a01e5b 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@actual-app/api", - "version": "6.3.0", + "version": "6.4.0", "license": "MIT", "description": "An API for Actual", "engines": { @@ -20,7 +20,7 @@ "test": "yarn run build:app && jest -c jest.config.js" }, "dependencies": { - "better-sqlite3": "^9.1.1", + "better-sqlite3": "^9.2.2", "compare-versions": "^6.1.0", "node-fetch": "^3.3.2", "uuid": "^9.0.0" diff --git a/packages/desktop-client/.gitignore b/packages/desktop-client/.gitignore index 7a5faec2c43..08fad3425bd 100644 --- a/packages/desktop-client/.gitignore +++ b/packages/desktop-client/.gitignore @@ -10,11 +10,13 @@ test-results # production build build-stats +stats.json # misc .DS_Store .env npm-debug.log +.swc *kcab.* public/kcab diff --git a/packages/desktop-client/README.md b/packages/desktop-client/README.md index 834b79b486a..2c6572f9fed 100644 --- a/packages/desktop-client/README.md +++ b/packages/desktop-client/README.md @@ -37,14 +37,15 @@ First start the dev server: ```sh HTTPS=true yarn start ``` + Next, navigate to the root of your project folder, run the standartised docker container, and launch the visual regression tests from within it. ```sh # Run docker container -docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.37.0-jammy /bin/bash +docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.37.1-jammy /bin/bash - # If you recieve an error such as "docker: invalid reference format", please instead use the following command: - docker run --rm --network host -v ${pwd}:/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.37.0-jammy /bin/bash + # If you receive an error such as "docker: invalid reference format", please instead use the following command: + docker run --rm --network host -v ${pwd}:/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.37.1-jammy /bin/bash # Run the VRT tests: important - they MUST be ran against a HTTPS server E2E_START_URL=https://192.168.0.178:3001 yarn vrt diff --git a/packages/desktop-client/bin/build-browser b/packages/desktop-client/bin/build-browser index 37fddd17f27..3e6e18aa6c0 100755 --- a/packages/desktop-client/bin/build-browser +++ b/packages/desktop-client/bin/build-browser @@ -8,7 +8,6 @@ echo "Building the browser..." rm -fr build export IS_GENERIC_BROWSER=1 -export INLINE_RUNTIME_CHUNK=false export REACT_APP_BACKEND_WORKER_HASH=`ls "$ROOT"/../public/kcab/kcab.worker.*.js | sed 's/.*kcab\.worker\.\(.*\)\.js/\1/'` yarn build @@ -16,4 +15,4 @@ yarn build rm -fr build-stats mkdir build-stats mv build/kcab/stats.json build-stats/loot-core-stats.json -mv build/stats.json build-stats/desktop-client-stats.json +mv ./stats.json build-stats/web-stats.json diff --git a/packages/desktop-client/craco.config.ts b/packages/desktop-client/craco.config.ts deleted file mode 100644 index 42a428979a2..00000000000 --- a/packages/desktop-client/craco.config.ts +++ /dev/null @@ -1,116 +0,0 @@ -const path = require('path'); - -const { - loaderByName, - removeLoaders, - addAfterLoader, - addPlugins, -} = require('@craco/craco'); -const chokidar = require('chokidar'); -const TerserPlugin = require('terser-webpack-plugin'); -const { IgnorePlugin } = require('webpack'); -const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); - -if (process.env.CI) { - process.env.DISABLE_ESLINT_PLUGIN = 'true'; -} - -// Forward Netlify env variables -if (process.env.REVIEW_ID) { - process.env.REACT_APP_REVIEW_ID = process.env.REVIEW_ID; -} - -module.exports = { - webpack: { - configure: (webpackConfig, { env, paths }) => { - webpackConfig.mode = - process.env.NODE_ENV === 'development' ? 'development' : 'production'; - - // swc-loader - addAfterLoader(webpackConfig, loaderByName('babel-loader'), { - test: /\.m?[tj]sx?$/, - exclude: /node_modules/, - loader: require.resolve('swc-loader'), - }); - - // remove the babel loaders - removeLoaders(webpackConfig, loaderByName('babel-loader')); - - addPlugins(webpackConfig, [ - new BundleAnalyzerPlugin({ - analyzerMode: 'disabled', - generateStatsFile: true, - }), - // Pikaday throws a warning if Moment.js is not installed however it doesn't - // actually require it to be installed. As we don't use Moment.js ourselves - // then we can just silence this warning. - new IgnorePlugin({ - contextRegExp: /pikaday$/, - resourceRegExp: /moment$/, - }), - ]); - - webpackConfig.resolve.extensions = [ - '.web.js', - '.web.jsx', - '.web.ts', - '.web.tsx', - '.js', - '.jsx', - '.ts', - '.tsx', - ...webpackConfig.resolve.extensions, - ]; - - if (process.env.IS_GENERIC_BROWSER) { - webpackConfig.resolve.extensions = [ - '.browser.js', - '.browser.jsx', - '.browser.ts', - '.browser.tsx', - ...webpackConfig.resolve.extensions, - ]; - } - - webpackConfig.optimization = { - ...webpackConfig.optimization, - minimize: - process.env.CI === 'true' || process.env.NODE_ENV !== 'development', - minimizer: [ - new TerserPlugin({ - minify: TerserPlugin.swcMinify, - // `terserOptions` options will be passed to `swc` (`@swc/core`) - // Link to options - https://swc.rs/docs/config-js-minify - terserOptions: { - compress: false, - mangle: true, - }, - }), - ], - }; - - return webpackConfig; - }, - }, - devServer: (devServerConfig, { env, paths, proxy, allowedHost }) => { - devServerConfig.onBeforeSetupMiddleware = server => { - chokidar - .watch([ - path.resolve('../loot-core/lib-dist/*.js'), - path.resolve('../loot-core/lib-dist/browser/*.js'), - ]) - .on('all', function () { - for (const ws of server.webSocketServer.clients) { - ws.send(JSON.stringify({ type: 'static-changed' })); - } - }); - }; - devServerConfig.headers = { - ...devServerConfig.headers, - 'Cross-Origin-Opener-Policy': 'same-origin', - 'Cross-Origin-Embedder-Policy': 'require-corp', - }; - - return devServerConfig; - }, -}; diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png index ee3954dd3f7..5e5f6c3febb 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png index 136f760a312..4de0dfb4895 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-3-chromium-linux.png b/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-3-chromium-linux.png index ed62336bdf9..32f89ebccab 100644 Binary files a/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-3-chromium-linux.png and b/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-4-chromium-linux.png b/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-4-chromium-linux.png index b3cea84a41e..0f71093688d 100644 Binary files a/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-4-chromium-linux.png and b/packages/desktop-client/e2e/onboarding.test.js-snapshots/Onboarding-checks-the-page-visuals-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png index cf8cc81acb3..6ad0736f6f5 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png index 855c0492000..4052e48c433 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png differ diff --git a/packages/desktop-client/public/index.html b/packages/desktop-client/index.html similarity index 88% rename from packages/desktop-client/public/index.html rename to packages/desktop-client/index.html index 08590d33006..e136b97dca1 100644 --- a/packages/desktop-client/public/index.html +++ b/packages/desktop-client/index.html @@ -8,25 +8,25 @@ />