From 090345bd95d107cc2161fcd253136c2da13111a1 Mon Sep 17 00:00:00 2001 From: Roger Goldfinger Date: Mon, 5 May 2025 20:45:49 -0700 Subject: [PATCH] Enable Typescript in sync-server (#4887) * attempt at running with typescript * release notes * working jest tests for TS files * working docker image build * remaining docker images * cleanup * ensure vitest is working * get tests passing in ci * less strict * update release notes * use tsc compiled assets in the published package * scripts * update yarn.lock * Use build path for electron app * PR feedback: move sync-server build out of bin/build-browser * PR feedback: undo moduleResolution change * extend main tsconfig and fix types * PR feedback on scripts and when the sync-server build runs * fix lint (unrelated change) --------- Co-authored-by: alecbakholdin --- .github/workflows/build.yml | 4 +-- .github/workflows/docker-edge.yml | 2 +- .github/workflows/docker-release.yml | 2 +- .github/workflows/netlify-release.yml | 2 +- .github/workflows/publish-npm-packages.yml | 2 +- bin/package-electron | 1 + package.json | 2 +- packages/desktop-electron/index.ts | 1 + packages/sync-server/{app.js => app.ts} | 0 packages/sync-server/docker/alpine.Dockerfile | 5 ++-- packages/sync-server/docker/ubuntu.Dockerfile | 5 ++-- packages/sync-server/package.json | 25 ++++++++--------- .../{app-sync.test.js => app-sync.test.ts} | 1 + .../src/{app-sync.js => app-sync.ts} | 9 ++++--- packages/sync-server/src/{app.js => app.ts} | 16 ++++++----- packages/sync-server/src/load-config.js | 4 ++- packages/sync-server/src/migrations.js | 2 +- .../src/{ => scripts}/run-migrations.js | 2 +- packages/sync-server/tsconfig.json | 27 +++++++++---------- sync-server.Dockerfile | 15 +++++------ upcoming-release-notes/4887.md | 6 +++++ yarn.lock | 2 +- 22 files changed, 71 insertions(+), 64 deletions(-) rename packages/sync-server/{app.js => app.ts} (100%) rename packages/sync-server/src/{app-sync.test.js => app-sync.test.ts} (99%) rename packages/sync-server/src/{app-sync.js => app-sync.ts} (98%) rename packages/sync-server/src/{app.js => app.ts} (90%) rename packages/sync-server/src/{ => scripts}/run-migrations.js (77%) create mode 100644 upcoming-release-notes/4887.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e52bc9748..955580ac7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: - name: Set up environment uses: ./.github/actions/setup - name: Build Web - run: ./bin/package-browser + run: yarn build:browser - name: Upload Build uses: actions/upload-artifact@v4 with: @@ -76,7 +76,7 @@ jobs: - name: Set up environment uses: ./.github/actions/setup - name: Build Server - run: cd packages/sync-server && yarn build + run: yarn workspace @actual-app/sync-server build - name: Upload Build uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/docker-edge.yml b/.github/workflows/docker-edge.yml index a43308cdc0..b4e8f0c6e4 100644 --- a/.github/workflows/docker-edge.yml +++ b/.github/workflows/docker-edge.yml @@ -78,7 +78,7 @@ jobs: - name: Set up environment uses: ./.github/actions/setup - name: Build Web - run: ./bin/package-browser + run: yarn build:server - name: Build and push image uses: docker/build-push-action@v5 diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index ccc6b4d886..9ba5a1c318 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -75,7 +75,7 @@ jobs: - name: Set up environment uses: ./.github/actions/setup - name: Build Web - run: ./bin/package-browser + run: yarn build:server - name: Build and push ubuntu image uses: docker/build-push-action@v5 diff --git a/.github/workflows/netlify-release.yml b/.github/workflows/netlify-release.yml index 003947b60f..649054132c 100644 --- a/.github/workflows/netlify-release.yml +++ b/.github/workflows/netlify-release.yml @@ -30,7 +30,7 @@ jobs: run: npm install netlify-cli@17.10.1 -g - name: Build Actual - run: ./bin/package-browser + run: yarn build:browser - name: Deploy to Netlify id: netlify_deploy diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 6eb79b16ee..b5dfb491a0 100644 --- a/.github/workflows/publish-npm-packages.yml +++ b/.github/workflows/publish-npm-packages.yml @@ -17,7 +17,7 @@ jobs: uses: ./.github/actions/setup - name: Build Web - run: yarn build:browser + run: yarn build:server - name: Pack the web and server packages run: | diff --git a/bin/package-electron b/bin/package-electron index e609718b80..9a01d44d10 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -45,6 +45,7 @@ yarn workspace @actual-app/web build --mode=desktop # electron specific build # required for running the sync-server server yarn workspace loot-core build:browser yarn workspace @actual-app/web build:browser +yarn workspace @actual-app/sync-server build yarn workspace desktop-electron update-client diff --git a/package.json b/package.json index f857ca959f..c1db507de8 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "start:browser": "npm-run-all --parallel 'start:browser-*'", "start:browser-backend": "yarn workspace loot-core watch:browser", "start:browser-frontend": "yarn workspace @actual-app/web start:browser", - "build:server": "yarn build:browser", + "build:server": "yarn build:browser && yarn workspace @actual-app/sync-server build", "build:browser": "./bin/package-browser", "build:desktop": "./bin/package-electron", "build:api": "yarn workspace @actual-app/api build", diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 65b8baf232..730ff8b02c 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -228,6 +228,7 @@ async function startSyncServer() { const serverPath = path.join( // require.resolve will recursively search up the workspace for the module path.dirname(require.resolve('@actual-app/sync-server/package.json')), + 'build', 'app.js', ); diff --git a/packages/sync-server/app.js b/packages/sync-server/app.ts similarity index 100% rename from packages/sync-server/app.js rename to packages/sync-server/app.ts diff --git a/packages/sync-server/docker/alpine.Dockerfile b/packages/sync-server/docker/alpine.Dockerfile index 9f1a82953b..72b9f4fa35 100644 --- a/packages/sync-server/docker/alpine.Dockerfile +++ b/packages/sync-server/docker/alpine.Dockerfile @@ -53,9 +53,8 @@ ENV NODE_ENV=production # Pull in only the necessary artifacts (built node_modules, server files, etc.) COPY --from=builder /app/node_modules /app/node_modules -COPY --from=builder /app/packages/sync-server/package.json /app/packages/sync-server/app.js ./ -COPY --from=builder /app/packages/sync-server/src ./src -COPY --from=builder /app/packages/sync-server/migrations ./migrations +COPY --from=builder /app/packages/sync-server/package.json ./ +COPY --from=builder /app/packages/sync-server/build ./ ENTRYPOINT ["/sbin/tini","-g", "--"] EXPOSE 5006 diff --git a/packages/sync-server/docker/ubuntu.Dockerfile b/packages/sync-server/docker/ubuntu.Dockerfile index 104e071082..47a1f61810 100644 --- a/packages/sync-server/docker/ubuntu.Dockerfile +++ b/packages/sync-server/docker/ubuntu.Dockerfile @@ -54,9 +54,8 @@ ENV NODE_ENV=production # Pull in only the necessary artifacts (built node_modules, server files, etc.) COPY --from=builder /app/node_modules /app/node_modules -COPY --from=builder /app/packages/sync-server/package.json /app/packages/sync-server/app.js ./ -COPY --from=builder /app/packages/sync-server/src ./src -COPY --from=builder /app/packages/sync-server/migrations ./migrations +COPY --from=builder /app/packages/sync-server/package.json ./ +COPY --from=builder /app/packages/sync-server/build ./ ENTRYPOINT ["/usr/bin/tini","-g", "--"] EXPOSE 5006 diff --git a/packages/sync-server/package.json b/packages/sync-server/package.json index 72d69179b0..1fca0980b5 100644 --- a/packages/sync-server/package.json +++ b/packages/sync-server/package.json @@ -4,30 +4,27 @@ "license": "MIT", "description": "actual syncing server", "bin": { - "actual-server": "./bin/actual-server.js" + "actual-server": "./build/bin/actual-server.js" }, "type": "module", "files": [ - "bin", - "src", - "app.js", - "migrations", + "build", "default-db.sqlite", "README.md", "LICENSE" ], "scripts": { - "start": "node app", - "start-monitor": "nodemon app", + "start": "yarn build && node build/app", + "start-monitor": "nodemon --exec 'tsc && node build/app' --ignore './build/**/*' --ext 'ts,js' build/app", "build": "tsc", "test": "NODE_ENV=test NODE_OPTIONS='--experimental-vm-modules --trace-warnings' vitest", - "db:migrate": "NODE_ENV=development node src/run-migrations.js up", - "db:downgrade": "NODE_ENV=development node src/run-migrations.js down", - "db:test-migrate": "NODE_ENV=test node src/run-migrations.js up", - "db:test-downgrade": "NODE_ENV=test node src/run-migrations.js down", - "reset-password": "node src/scripts/reset-password.js", - "disable-openid": "node src/scripts/disable-openid.js", - "health-check": "node src/scripts/health-check.js" + "db:migrate": "yarn build && cross-env NODE_ENV=development node build/src/scripts/run-migrations.js up", + "db:downgrade": "yarn build && cross-env NODE_ENV=development node build/src/scripts/run-migrations.js down", + "db:test-migrate": "yarn build && cross-env NODE_ENV=test node build/src/scripts/run-migrations.js up", + "db:test-downgrade": "yarn build && cross-env NODE_ENV=test node build/src/scripts/run-migrations.js down", + "reset-password": "yarn build && node build/src/scripts/reset-password.js", + "disable-openid": "yarn build && node build/src/scripts/disable-openid.js", + "health-check": "yarn build && node build/src/scripts/health-check.js" }, "dependencies": { "@actual-app/crdt": "2.1.0", diff --git a/packages/sync-server/src/app-sync.test.js b/packages/sync-server/src/app-sync.test.ts similarity index 99% rename from packages/sync-server/src/app-sync.test.js rename to packages/sync-server/src/app-sync.test.ts index cf31da927e..e8b4509716 100644 --- a/packages/sync-server/src/app-sync.test.js +++ b/packages/sync-server/src/app-sync.test.ts @@ -1,3 +1,4 @@ +// @ts-strict-ignore import crypto from 'node:crypto'; import fs from 'node:fs'; diff --git a/packages/sync-server/src/app-sync.js b/packages/sync-server/src/app-sync.ts similarity index 98% rename from packages/sync-server/src/app-sync.js rename to packages/sync-server/src/app-sync.ts index 92a354d4e1..9ed089d767 100644 --- a/packages/sync-server/src/app-sync.js +++ b/packages/sync-server/src/app-sync.ts @@ -1,3 +1,4 @@ +// @ts-strict-ignore import { Buffer } from 'node:buffer'; import fs from 'node:fs/promises'; @@ -55,7 +56,7 @@ const verifyFileExists = (fileId, filesService, res, errorObject) => { } }; -app.post('/sync', async (req, res) => { +app.post('/sync', async (req, res): Promise => { let requestPb; try { requestPb = SyncProtoBuf.SyncRequest.deserializeBinary(req.body); @@ -73,11 +74,12 @@ app.post('/sync', async (req, res) => { const messages = requestPb.getMessagesList(); if (!since) { - return res.status(422).send({ + res.status(422).send({ details: 'since-required', reason: 'unprocessable-entity', status: 'error', }); + return; } const filesService = new FilesService(getAccountDb()); @@ -373,11 +375,12 @@ app.post('/delete-user-file', (req, res) => { const { fileId } = req.body; if (!fileId) { - return res.status(422).send({ + res.status(422).send({ details: 'fileId-required', reason: 'unprocessable-entity', status: 'error', }); + return; } const filesService = new FilesService(getAccountDb()); diff --git a/packages/sync-server/src/app.js b/packages/sync-server/src/app.ts similarity index 90% rename from packages/sync-server/src/app.js rename to packages/sync-server/src/app.ts index efcda06dff..1321427f13 100644 --- a/packages/sync-server/src/app.js +++ b/packages/sync-server/src/app.ts @@ -88,7 +88,6 @@ if (process.env.NODE_ENV === 'development') { target: 'http://localhost:3001', changeOrigin: true, ws: true, - logLevel: 'debug', }), ); } else { @@ -100,7 +99,7 @@ if (process.env.NODE_ENV === 'development') { ); } -function parseHTTPSConfig(value) { +function parseHTTPSConfig(value: string) { if (value.startsWith('-----BEGIN')) { return value; } @@ -108,9 +107,13 @@ function parseHTTPSConfig(value) { } export async function run() { + const portVal = config.get('port'); + const port = typeof portVal === 'string' ? parseInt(portVal) : portVal; + const hostname = config.get('hostname'); const openIdConfig = config?.getProperties()?.openId; if ( openIdConfig?.discoveryURL || + // @ts-expect-error FIXME no types for config yet openIdConfig?.issuer?.authorization_endpoint ) { console.log('OpenID configuration found. Preparing server to use it'); @@ -129,18 +132,17 @@ export async function run() { if (config.get('https.key') && config.get('https.cert')) { const https = await import('node:https'); const httpsOptions = { - ...config.https, + ...config.get('https'), key: parseHTTPSConfig(config.get('https.key')), cert: parseHTTPSConfig(config.get('https.cert')), }; - https - .createServer(httpsOptions, app) - .listen(config.get('port'), config.get('hostname')); + https.createServer(httpsOptions, app).listen(port, hostname); } else { - app.listen(config.get('port'), config.get('hostname')); + app.listen(port, hostname); } // Signify to any parent process that the server has started. Used in electron desktop app + // @ts-ignore-error electron types process.parentPort?.postMessage({ type: 'server-started' }); console.log( diff --git a/packages/sync-server/src/load-config.js b/packages/sync-server/src/load-config.js index f3b20ce5d8..1ac7c12ad7 100644 --- a/packages/sync-server/src/load-config.js +++ b/packages/sync-server/src/load-config.js @@ -10,7 +10,9 @@ const require = createRequire(import.meta.url); const debug = createDebug('actual:config'); const debugSensitive = createDebug('actual-sensitive:config'); -const projectRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const projectRoot = path + .dirname(path.dirname(fileURLToPath(import.meta.url))) + .replace(/[\\/]build$/, ''); const defaultDataDir = process.env.ACTUAL_DATA_DIR ? process.env.ACTUAL_DATA_DIR : fs.existsSync('/data') diff --git a/packages/sync-server/src/migrations.js b/packages/sync-server/src/migrations.js index 9160c5ce50..c900e2b36c 100644 --- a/packages/sync-server/src/migrations.js +++ b/packages/sync-server/src/migrations.js @@ -15,7 +15,7 @@ export function run(direction = 'up') { stateStore: `${path.join(config.get('dataDir'), '.migrate')}${ config.get('mode') === 'test' ? '-test' : '' }`, - migrationsDirectory: `${path.join(config.get('projectRoot'), 'migrations')}`, + migrationsDirectory: `${path.join(config.get('projectRoot'), config.get('mode') === 'test' ? '' : 'build', 'migrations')}`, }, (err, set) => { if (err) { diff --git a/packages/sync-server/src/run-migrations.js b/packages/sync-server/src/scripts/run-migrations.js similarity index 77% rename from packages/sync-server/src/run-migrations.js rename to packages/sync-server/src/scripts/run-migrations.js index 5fcf4231ec..b76def17c6 100644 --- a/packages/sync-server/src/run-migrations.js +++ b/packages/sync-server/src/scripts/run-migrations.js @@ -1,4 +1,4 @@ -import { run } from './migrations.js'; +import { run } from '../migrations.js'; const direction = process.argv[2] || 'up'; diff --git a/packages/sync-server/tsconfig.json b/packages/sync-server/tsconfig.json index 73c81b9687..d88ba24cfb 100644 --- a/packages/sync-server/tsconfig.json +++ b/packages/sync-server/tsconfig.json @@ -1,22 +1,19 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "target": "ES2022", // DOM for URL global in Node 16+ "lib": ["ES2021"], - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "experimentalDecorators": true, - "resolveJsonModule": true, - "downlevelIteration": true, - "skipLibCheck": true, - "jsx": "preserve", - // Check JS files too - "allowJs": true, - "moduleResolution": "node16", "module": "node16", - "outDir": "build", - "types": ["vite/client", "vitest/globals"] + "moduleResolution": "node16", + "noEmit": false, + "outDir": "build" }, - "include": ["src/**/*.js", "types/global.d.ts"], - "exclude": ["node_modules", "build", "./app-plaid.js", "coverage"] + "include": [ + "src/**/*", + "migrations/**/*", + "types/**/*", + "app.ts", + "bin/**/*" + ], + "exclude": ["node_modules", "build", "coverage"] } diff --git a/sync-server.Dockerfile b/sync-server.Dockerfile index 5f501e5f55..e4f113b46b 100644 --- a/sync-server.Dockerfile +++ b/sync-server.Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-bookworm as deps +FROM node:18-bookworm AS deps # Install required packages RUN apt-get update && apt-get install -y openssl @@ -21,12 +21,12 @@ COPY ./bin/package-browser ./bin/package-browser RUN yarn install -FROM deps as builder +FROM deps AS builder WORKDIR /app COPY packages/ ./packages/ -RUN yarn build:browser +RUN yarn build:server # Focus the workspaces in production mode (including @actual-app/web you just built) RUN yarn workspaces focus @actual-app/sync-server --production @@ -38,7 +38,7 @@ RUN rm -rf ./node_modules/@actual-app/web ./node_modules/@actual-app/sync-server COPY ./packages/desktop-client/package.json ./node_modules/@actual-app/web/package.json RUN cp -r ./packages/desktop-client/build ./node_modules/@actual-app/web/build -FROM node:18-bookworm-slim as prod +FROM node:18-bookworm-slim AS prod # Minimal runtime dependencies RUN apt-get update && apt-get install -y tini && apt-get clean -y && rm -rf /var/lib/apt/lists/* @@ -56,10 +56,9 @@ ENV NODE_ENV=production # Pull in only the necessary artifacts (built node_modules, server files, etc.) COPY --from=builder /app/node_modules /app/node_modules -COPY --from=builder /app/packages/sync-server/package.json /app/packages/sync-server/app.js ./ -COPY --from=builder /app/packages/sync-server/src ./src -COPY --from=builder /app/packages/sync-server/migrations ./migrations +COPY --from=builder /app/packages/sync-server/package.json ./ +COPY --from=builder /app/packages/sync-server/build ./build ENTRYPOINT ["/usr/bin/tini", "-g", "--"] EXPOSE 5006 -CMD ["node", "app.js"] +CMD ["node", "build/app.js"] diff --git a/upcoming-release-notes/4887.md b/upcoming-release-notes/4887.md new file mode 100644 index 0000000000..6051e15cf1 --- /dev/null +++ b/upcoming-release-notes/4887.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [rgoldfinger] +--- + +Converted sync-server to run with typescript diff --git a/yarn.lock b/yarn.lock index aaed54ca1a..9a19f494d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,7 +121,7 @@ __metadata: vitest: "npm:^3.0.2" winston: "npm:^3.17.0" bin: - actual-server: ./bin/actual-server.js + actual-server: ./build/bin/actual-server.js languageName: unknown linkType: soft