Migrate get-next-package-version.js to TypeScript (#7227)

* Migrate `get-next-package-version.js` to TypeScript

* Add release notes

* Stronger type check

* Fix step ordering

* Fix typo

* Fix missed ordering
This commit is contained in:
Julian Dominguez-Schatz
2026-03-17 09:56:47 -04:00
committed by GitHub
parent 4f7c3c51a5
commit e1606b31ab
11 changed files with 114 additions and 50 deletions

View File

@@ -42,6 +42,8 @@ jobs:
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate source .venv/bin/activate
python3 -m pip install setuptools python3 -m pip install setuptools
- name: Set up environment
uses: ./.github/actions/setup
- if: ${{ startsWith(matrix.os, 'ubuntu') }} - if: ${{ startsWith(matrix.os, 'ubuntu') }}
name: Setup Flatpak dependencies name: Setup Flatpak dependencies
run: | run: |
@@ -56,11 +58,9 @@ jobs:
METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml" METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml"
TODAY=$(date +%Y-%m-%d) 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" 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" flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream "$METAINFO_FILE"
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Electron - name: Build Electron
run: ./bin/package-electron run: ./bin/package-electron

View File

@@ -20,6 +20,10 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
ref: ${{ github.event.inputs.ref }} ref: ${{ github.event.inputs.ref }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Bump package versions - name: Bump package versions
id: bump_package_versions id: bump_package_versions
shell: bash shell: bash
@@ -35,12 +39,12 @@ jobs:
pkg="${packages[$key]}" pkg="${packages[$key]}"
if [[ -n "${{ github.event.inputs.version }}" ]]; then 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" \ --package-json "./packages/$pkg/package.json" \
--version "${{ github.event.inputs.version }}" \ --version "${{ github.event.inputs.version }}" \
--update) --update)
else 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" \ --package-json "./packages/$pkg/package.json" \
--type auto \ --type auto \
--update) --update)

View File

@@ -39,6 +39,9 @@ jobs:
source .venv/bin/activate source .venv/bin/activate
python3 -m pip install setuptools python3 -m pip install setuptools
- name: Set up environment
uses: ./.github/actions/setup
- if: ${{ startsWith(matrix.os, 'ubuntu') }} - if: ${{ startsWith(matrix.os, 'ubuntu') }}
name: Setup Flatpak dependencies name: Setup Flatpak dependencies
run: | run: |
@@ -53,16 +56,14 @@ jobs:
METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml" METAINFO_FILE="packages/desktop-electron/extra-resources/linux/com.actualbudget.actual.metainfo.xml"
TODAY=$(date +%Y-%m-%d) 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" 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" flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream "$METAINFO_FILE"
- name: Set up environment
uses: ./.github/actions/setup
- name: Update package versions - name: Update package versions
run: | run: |
# Get new nightly version # 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 # Set package version
npm version $NEW_DESKTOP_APP_VERSION --no-git-tag-version --workspace=desktop-electron --no-workspaces-update npm version $NEW_DESKTOP_APP_VERSION --no-git-tag-version --workspace=desktop-electron --no-workspaces-update

View File

@@ -20,10 +20,10 @@ jobs:
- name: Update package versions - name: Update package versions
run: | run: |
# Get new nightly versions # 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_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=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/desktop-client/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=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/sync-server/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=$(node ./packages/ci-actions/bin/get-next-package-version.js --package-json ./packages/api/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 # Set package versions
npm version $NEW_CORE_VERSION --no-git-tag-version --workspace=@actual-app/core --no-workspaces-update npm version $NEW_CORE_VERSION --no-git-tag-version --workspace=@actual-app/core --no-workspaces-update

View File

@@ -2,13 +2,13 @@
// This script is used in GitHub Actions to get the next version based on the current package.json version. // 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. // It supports three types of versioning: nightly, hotfix, and monthly.
import fs from 'node:fs'; import fs from 'node:fs';
import { parseArgs } from 'node:util'; import { parseArgs } from 'node:util';
import { getNextVersion } from '../src/versions/get-next-package-version.js'; import {
getNextVersion,
const args = process.argv; isValidVersionType,
} from '../src/versions/get-next-package-version';
const options = { const options = {
'package-json': { 'package-json': {
@@ -28,40 +28,53 @@ const options = {
short: 'u', short: 'u',
default: false, default: false,
}, },
}; } as const;
function fail(message: string): never {
console.error(message);
process.exit(1);
}
const { values } = parseArgs({ const { values } = parseArgs({
args,
options, options,
allowPositionals: true, allowPositionals: true,
}); });
if (!values['package-json']) { const packageJsonPath = values['package-json'];
console.error( if (!packageJsonPath) {
fail(
'Please specify the path to package.json using --package-json or -p option.', 'Please specify the path to package.json using --package-json or -p option.',
); );
process.exit(1);
} }
try { try {
const packageJsonPath = values['package-json'];
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); 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 currentVersion = packageJson.version;
const explicitVersion = values.version; const explicitVersion = values.version;
let newVersion; let newVersion;
if (explicitVersion) { if (explicitVersion) {
newVersion = explicitVersion; newVersion = explicitVersion;
} else { } else {
const type = values.type;
if (!type || !isValidVersionType(type)) {
fail('Please specify the release type using --type or -t.');
}
try { try {
newVersion = getNextVersion({ newVersion = getNextVersion({
currentVersion, currentVersion,
type: values.type, type,
currentDate: new Date(), currentDate: new Date(),
}); });
} catch (e) { } catch (error) {
console.error(e.message); fail(error instanceof Error ? error.message : String(error));
process.exit(1);
} }
} }
@@ -76,6 +89,5 @@ try {
); );
} }
} catch (error) { } catch (error) {
console.error('Error:', error.message); fail(`Error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
} }

8
packages/ci-actions/bin/tsx Executable file
View File

@@ -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" "$@"

View File

@@ -3,7 +3,7 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"tsx": "node --import=extensionless/register --experimental-strip-types", "tsx": "bin/tsx",
"test": "vitest --run", "test": "vitest --run",
"typecheck": "tsgo -b" "typecheck": "tsgo -b"
}, },

View File

@@ -77,7 +77,7 @@ describe('getNextVersion (lib)', () => {
expect(() => expect(() =>
getNextVersion({ getNextVersion({
currentVersion: '25.8.4', currentVersion: '25.8.4',
type: 'unknown', type: 'unknown' as never,
currentDate: new Date('2025-08-10'), currentDate: new Date('2025-08-10'),
}), }),
).toThrow(/Invalid type/); ).toThrow(/Invalid type/);

View File

@@ -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('.'); const [y, m, p] = version.split('.');
return { return {
versionYear: parseInt(y, 10), versionYear: Number.parseInt(y, 10),
versionMonth: parseInt(m, 10), versionMonth: Number.parseInt(m, 10),
versionHotfix: parseInt(p, 10), versionHotfix: Number.parseInt(p, 10),
}; };
} }
function computeNextMonth(versionYear, versionMonth) { function computeNextMonth(versionYear: number, versionMonth: number) {
// Create date and add 1 month const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1);
const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1); // month is 0-indexed
const nextVersionMonthDate = new Date( const nextVersionMonthDate = new Date(
versionDate.getFullYear(), versionDate.getFullYear(),
versionDate.getMonth() + 1, versionDate.getMonth() + 1,
1, 1,
); );
// Format back to YY.M format
const fullYear = nextVersionMonthDate.getFullYear(); const fullYear = nextVersionMonthDate.getFullYear();
const nextVersionYear = fullYear.toString().slice(fullYear < 2100 ? -2 : -3); 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 }; return { nextVersionYear, nextVersionMonth };
} }
// Determine logical type from 'auto' based on the current date and version export function isValidVersionType(value: string): value is VersionType {
function resolveType(type, currentDate, versionYear, versionMonth) { return versionTypeArray.includes(value as VersionType);
if (type !== 'auto') return type; }
function resolveType(
type: VersionType,
currentDate: Date,
versionYear: number,
versionMonth: number,
) {
if (type !== 'auto') {
return type;
}
const inPatchMonth = const inPatchMonth =
currentDate.getFullYear() === 2000 + versionYear && currentDate.getFullYear() === 2000 + versionYear &&
currentDate.getMonth() + 1 === versionMonth; currentDate.getMonth() + 1 === versionMonth;
if (inPatchMonth && currentDate.getDate() <= 25) return 'hotfix';
if (inPatchMonth && currentDate.getDate() <= 25) {
return 'hotfix';
}
return 'monthly'; return 'monthly';
} }
@@ -37,7 +71,7 @@ export function getNextVersion({
currentVersion, currentVersion,
type, type,
currentDate = new Date(), currentDate = new Date(),
}) { }: GetNextVersionOptions) {
const { versionYear, versionMonth, versionHotfix } = const { versionYear, versionMonth, versionHotfix } =
parseVersion(currentVersion); parseVersion(currentVersion);
const { nextVersionYear, nextVersionMonth } = computeNextMonth( const { nextVersionYear, nextVersionMonth } = computeNextMonth(
@@ -51,11 +85,10 @@ export function getNextVersion({
versionMonth, versionMonth,
); );
// Format date stamp once for nightly
const currentDateString = currentDate const currentDateString = currentDate
.toISOString() .toISOString()
.split('T')[0] .split('T')[0]
.replaceAll('-', ''); .replace(/-/g, '');
switch (resolvedType) { switch (resolvedType) {
case 'nightly': case 'nightly':
@@ -66,7 +99,7 @@ export function getNextVersion({
return `${nextVersionYear}.${nextVersionMonth}.0`; return `${nextVersionYear}.${nextVersionMonth}.0`;
default: default:
throw new Error( 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".`,
); );
} }
} }

View File

@@ -2,8 +2,8 @@
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"lib": [], "lib": [],
"module": "nodenext", "module": "es2022",
"moduleResolution": "nodenext", "moduleResolution": "bundler",
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"types": ["node"], "types": ["node"],

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [jfdoming]
---
Migrate `get-next-package-version.js` to TypeScript