diff --git a/.github/workflows/electron-pr.yml b/.github/workflows/electron-pr.yml index 0b405c03e1..fb2cace81d 100644 --- a/.github/workflows/electron-pr.yml +++ b/.github/workflows/electron-pr.yml @@ -42,6 +42,8 @@ jobs: python3 -m venv .venv source .venv/bin/activate python3 -m pip install setuptools + - name: Set up environment + uses: ./.github/actions/setup - if: ${{ startsWith(matrix.os, 'ubuntu') }} name: Setup Flatpak dependencies run: | @@ -56,11 +58,9 @@ jobs: METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml" TODAY=$(date +%Y-%m-%d) - VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-electron/package.json --type nightly) + VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-electron/package.json --type nightly) sed -i "s/%RELEASE_VERSION%/$VERSION/g; s/%RELEASE_DATE%/$TODAY/g" "$METAINFO_FILE" flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream "$METAINFO_FILE" - - name: Set up environment - uses: ./.github/actions/setup - name: Build Electron run: ./bin/package-electron diff --git a/.github/workflows/generate-release-pr.yml b/.github/workflows/generate-release-pr.yml index 76363c954d..d6de8afad7 100644 --- a/.github/workflows/generate-release-pr.yml +++ b/.github/workflows/generate-release-pr.yml @@ -20,6 +20,10 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ github.event.inputs.ref }} + - name: Set up environment + uses: ./.github/actions/setup + with: + download-translations: 'false' - name: Bump package versions id: bump_package_versions shell: bash @@ -35,12 +39,12 @@ jobs: pkg="${packages[$key]}" if [[ -n "${{ github.event.inputs.version }}" ]]; then - version=$(node ./packages/ci-actions/bin/get-next-package-version.js \ + version=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts \ --package-json "./packages/$pkg/package.json" \ --version "${{ github.event.inputs.version }}" \ --update) else - version=$(node ./packages/ci-actions/bin/get-next-package-version.js \ + version=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts \ --package-json "./packages/$pkg/package.json" \ --type auto \ --update) diff --git a/.github/workflows/publish-nightly-electron.yml b/.github/workflows/publish-nightly-electron.yml index bbe443edb1..6ae84fc9f0 100644 --- a/.github/workflows/publish-nightly-electron.yml +++ b/.github/workflows/publish-nightly-electron.yml @@ -39,6 +39,9 @@ jobs: source .venv/bin/activate python3 -m pip install setuptools + - name: Set up environment + uses: ./.github/actions/setup + - if: ${{ startsWith(matrix.os, 'ubuntu') }} name: Setup Flatpak dependencies run: | @@ -53,16 +56,14 @@ jobs: METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml" TODAY=$(date +%Y-%m-%d) - VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-electron/package.json --type nightly) + VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-electron/package.json --type nightly) sed -i "s/%RELEASE_VERSION%/$VERSION/g; s/%RELEASE_DATE%/$TODAY/g" "$METAINFO_FILE" flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream "$METAINFO_FILE" - - name: Set up environment - uses: ./.github/actions/setup - name: Update package versions run: | # Get new nightly version - NEW_DESKTOP_APP_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-electron/package.json --type nightly) + NEW_DESKTOP_APP_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-electron/package.json --type nightly) # Set package version npm version $NEW_DESKTOP_APP_VERSION --no-git-tag-version --workspace=desktop-electron --no-workspaces-update diff --git a/.github/workflows/publish-nightly-npm-packages.yml b/.github/workflows/publish-nightly-npm-packages.yml index 9925d50557..0064289cd5 100644 --- a/.github/workflows/publish-nightly-npm-packages.yml +++ b/.github/workflows/publish-nightly-npm-packages.yml @@ -20,10 +20,10 @@ jobs: - name: Update package versions run: | # Get new nightly versions - NEW_CORE_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/loot-core/package.json --type nightly) - NEW_WEB_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-client/package.json --type nightly) - NEW_SYNC_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/sync-server/package.json --type nightly) - NEW_API_VERSION=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/api/package.json --type nightly) + NEW_CORE_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/loot-core/package.json --type nightly) + NEW_WEB_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/desktop-client/package.json --type nightly) + NEW_SYNC_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/sync-server/package.json --type nightly) + NEW_API_VERSION=$(yarn workspace @actual-app/ci-actions tsx bin/get-next-package-version.ts --package-json ./packages/api/package.json --type nightly) # Set package versions npm version $NEW_CORE_VERSION --no-git-tag-version --workspace=@actual-app/core --no-workspaces-update diff --git a/packages/ci-actions/bin/get-next-package-version.js b/packages/ci-actions/bin/get-next-package-version.ts old mode 100755 new mode 100644 similarity index 64% rename from packages/ci-actions/bin/get-next-package-version.js rename to packages/ci-actions/bin/get-next-package-version.ts index 99d5feb9aa..dfc911d60e --- a/packages/ci-actions/bin/get-next-package-version.js +++ b/packages/ci-actions/bin/get-next-package-version.ts @@ -2,13 +2,13 @@ // This script is used in GitHub Actions to get the next version based on the current package.json version. // It supports three types of versioning: nightly, hotfix, and monthly. - import fs from 'node:fs'; import { parseArgs } from 'node:util'; -import { getNextVersion } from '../src/versions/get-next-package-version.js'; - -const args = process.argv; +import { + getNextVersion, + isValidVersionType, +} from '../src/versions/get-next-package-version'; const options = { 'package-json': { @@ -28,40 +28,53 @@ const options = { short: 'u', default: false, }, -}; +} as const; + +function fail(message: string): never { + console.error(message); + process.exit(1); +} const { values } = parseArgs({ - args, options, allowPositionals: true, }); -if (!values['package-json']) { - console.error( +const packageJsonPath = values['package-json']; +if (!packageJsonPath) { + fail( 'Please specify the path to package.json using --package-json or -p option.', ); - process.exit(1); } try { - const packageJsonPath = values['package-json']; const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + if (!('version' in packageJson) || typeof packageJson.version !== 'string') { + fail('The specified package.json does not contain a valid version field.'); + } + const currentVersion = packageJson.version; const explicitVersion = values.version; let newVersion; + if (explicitVersion) { newVersion = explicitVersion; } else { + const type = values.type; + if (!type || !isValidVersionType(type)) { + fail('Please specify the release type using --type or -t.'); + } + try { newVersion = getNextVersion({ currentVersion, - type: values.type, + type, currentDate: new Date(), }); - } catch (e) { - console.error(e.message); - process.exit(1); + } catch (error) { + fail(error instanceof Error ? error.message : String(error)); } } @@ -76,6 +89,5 @@ try { ); } } catch (error) { - console.error('Error:', error.message); - process.exit(1); + fail(`Error: ${error instanceof Error ? error.message : String(error)}`); } diff --git a/packages/ci-actions/bin/tsx b/packages/ci-actions/bin/tsx new file mode 100755 index 0000000000..ccc755f6ba --- /dev/null +++ b/packages/ci-actions/bin/tsx @@ -0,0 +1,8 @@ +#!/bin/bash +set -euo pipefail + +cd ../../ + +script="$1" +shift +exec node --import=extensionless/register --experimental-strip-types packages/ci-actions/"$script" "$@" diff --git a/packages/ci-actions/package.json b/packages/ci-actions/package.json index 4b206ccabd..a6b262af9d 100644 --- a/packages/ci-actions/package.json +++ b/packages/ci-actions/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "tsx": "node --import=extensionless/register --experimental-strip-types", + "tsx": "bin/tsx", "test": "vitest --run", "typecheck": "tsgo -b" }, diff --git a/packages/ci-actions/src/versions/get-next-package-version.test.js b/packages/ci-actions/src/versions/get-next-package-version.test.ts similarity index 98% rename from packages/ci-actions/src/versions/get-next-package-version.test.js rename to packages/ci-actions/src/versions/get-next-package-version.test.ts index 9277a239d7..a6859e0739 100644 --- a/packages/ci-actions/src/versions/get-next-package-version.test.js +++ b/packages/ci-actions/src/versions/get-next-package-version.test.ts @@ -77,7 +77,7 @@ describe('getNextVersion (lib)', () => { expect(() => getNextVersion({ currentVersion: '25.8.4', - type: 'unknown', + type: 'unknown' as never, currentDate: new Date('2025-08-10'), }), ).toThrow(/Invalid type/); diff --git a/packages/ci-actions/src/versions/get-next-package-version.js b/packages/ci-actions/src/versions/get-next-package-version.ts similarity index 52% rename from packages/ci-actions/src/versions/get-next-package-version.js rename to packages/ci-actions/src/versions/get-next-package-version.ts index 5db401b945..ae3bf0fa3e 100644 --- a/packages/ci-actions/src/versions/get-next-package-version.js +++ b/packages/ci-actions/src/versions/get-next-package-version.ts @@ -1,35 +1,69 @@ -function parseVersion(version) { +export const versionTypeArray = [ + 'auto', + 'hotfix', + 'monthly', + 'nightly', +] as const; +export type VersionType = (typeof versionTypeArray)[number]; + +type ParsedVersion = { + versionYear: number; + versionMonth: number; + versionHotfix: number; +}; + +type GetNextVersionOptions = { + currentVersion: string; + type: VersionType; + currentDate?: Date; +}; + +function parseVersion(version: string): ParsedVersion { const [y, m, p] = version.split('.'); return { - versionYear: parseInt(y, 10), - versionMonth: parseInt(m, 10), - versionHotfix: parseInt(p, 10), + versionYear: Number.parseInt(y, 10), + versionMonth: Number.parseInt(m, 10), + versionHotfix: Number.parseInt(p, 10), }; } -function computeNextMonth(versionYear, versionMonth) { - // Create date and add 1 month - const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1); // month is 0-indexed +function computeNextMonth(versionYear: number, versionMonth: number) { + const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1); const nextVersionMonthDate = new Date( versionDate.getFullYear(), versionDate.getMonth() + 1, 1, ); - // Format back to YY.M format const fullYear = nextVersionMonthDate.getFullYear(); const nextVersionYear = fullYear.toString().slice(fullYear < 2100 ? -2 : -3); - const nextVersionMonth = nextVersionMonthDate.getMonth() + 1; // Convert back to 1-indexed + const nextVersionMonth = nextVersionMonthDate.getMonth() + 1; + return { nextVersionYear, nextVersionMonth }; } -// Determine logical type from 'auto' based on the current date and version -function resolveType(type, currentDate, versionYear, versionMonth) { - if (type !== 'auto') return type; +export function isValidVersionType(value: string): value is VersionType { + return versionTypeArray.includes(value as VersionType); +} + +function resolveType( + type: VersionType, + currentDate: Date, + versionYear: number, + versionMonth: number, +) { + if (type !== 'auto') { + return type; + } + const inPatchMonth = currentDate.getFullYear() === 2000 + versionYear && currentDate.getMonth() + 1 === versionMonth; - if (inPatchMonth && currentDate.getDate() <= 25) return 'hotfix'; + + if (inPatchMonth && currentDate.getDate() <= 25) { + return 'hotfix'; + } + return 'monthly'; } @@ -37,7 +71,7 @@ export function getNextVersion({ currentVersion, type, currentDate = new Date(), -}) { +}: GetNextVersionOptions) { const { versionYear, versionMonth, versionHotfix } = parseVersion(currentVersion); const { nextVersionYear, nextVersionMonth } = computeNextMonth( @@ -51,11 +85,10 @@ export function getNextVersion({ versionMonth, ); - // Format date stamp once for nightly const currentDateString = currentDate .toISOString() .split('T')[0] - .replaceAll('-', ''); + .replace(/-/g, ''); switch (resolvedType) { case 'nightly': @@ -66,7 +99,7 @@ export function getNextVersion({ return `${nextVersionYear}.${nextVersionMonth}.0`; default: throw new Error( - 'Invalid type specified. Use "auto", "nightly", "hotfix", or "monthly".', + `Invalid type ${String(resolvedType satisfies never)} specified. Use "auto", "nightly", "hotfix", or "monthly".`, ); } } diff --git a/packages/ci-actions/tsconfig.json b/packages/ci-actions/tsconfig.json index 8d34a03cad..27cc297dc5 100644 --- a/packages/ci-actions/tsconfig.json +++ b/packages/ci-actions/tsconfig.json @@ -2,8 +2,8 @@ "compilerOptions": { "target": "ES2022", "lib": [], - "module": "nodenext", - "moduleResolution": "nodenext", + "module": "es2022", + "moduleResolution": "bundler", "skipLibCheck": true, "strict": true, "types": ["node"], diff --git a/upcoming-release-notes/7227.md b/upcoming-release-notes/7227.md new file mode 100644 index 0000000000..5832bef35a --- /dev/null +++ b/upcoming-release-notes/7227.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [jfdoming] +--- + +Migrate `get-next-package-version.js` to TypeScript