Files
actual/packages/api/vite.config.mts
89384e0b7b [AI] Add getScheduleDates to the API (#9020)
* [AI] Add getScheduleDates to the API

Expand a schedule date (single date or RecurConfig) into the dates it
falls on within a window, honouring frequency, interval, monthly
patterns, end mode and the weekend move.

Also type the return value of recurConfigToRSchedule, and fix the API
vitest setup, where inlined UMD builds of @rschedule registered the
date adapter and recurrence rules on a second @rschedule/core instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CfQCMhYExnAqVNC4gJjF8E

* [AI] Build getScheduleDates on getNextDateAfter

Walk the schedule with getNextDateAfter, the step the app uses to
advance a schedule, instead of expanding the rschedule occurrences.
A date that several occurrences move onto (a daily schedule's Saturday
and Sunday both landing on Monday with skipWeekend) is now listed once,
and count counts distinct dates.

Add an API test that pins the first listed date to the next_date the
app stores, so the two cannot drift. Drop the recurConfigToRSchedule
typing change, which is no longer needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRp7PBTgAChF39Dv9iFLyc

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:48:02 +00:00

108 lines
3.3 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import {
defaultDbPath,
migrationsDir,
} from '@actual-app/core/default-filesystem';
import { peggyLoader } from '@actual-app/vite-plugin-peggy';
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';
import { configDefaults } from 'vitest/config';
const distDir = path.resolve(__dirname, 'dist');
const typesDir = path.resolve(__dirname, '@types');
function cleanOutputDirs() {
return {
name: 'clean-output-dirs',
buildStart() {
if (fs.existsSync(distDir)) fs.rmSync(distDir, { recursive: true });
if (fs.existsSync(typesDir)) fs.rmSync(typesDir, { recursive: true });
},
};
}
// The Node build reads migrations + the default DB from disk at runtime (see
// fs.migrationsPath / bundledDatabasePath in loot-core), so copy them next to
// the bundle. The browser build embeds its own copies, so nothing else needs to
// be on disk.
function copyNodeRuntimeAssets() {
return {
name: 'copy-node-runtime-assets',
closeBundle() {
fs.cpSync(migrationsDir, path.join(distDir, 'migrations'), {
recursive: true,
});
fs.copyFileSync(defaultDbPath, path.join(distDir, 'default-db.sqlite'));
},
};
}
export default defineConfig({
ssr: {
noExternal: true,
external: ['better-sqlite3'],
resolve: {
conditions: ['api'],
// Under vitest, `noExternal` inlines the UMD builds of @rschedule, but
// the `require()` calls inside them still load native copies, so
// `@rschedule/standard-date-adapter/setup` registers the date adapter
// and recurrence rules on a different `@rschedule/core` than the one
// loot-core uses, and every schedule then advances one millisecond at
// a time. Resolving the ES builds keeps a single instance. The
// production bundle is unaffected, as Rollup merges the copies.
...(process.env.VITEST ? { mainFields: ['module', 'main'] } : {}),
},
},
build: {
ssr: true,
target: 'node20',
outDir: distDir,
emptyOutDir: true,
sourcemap: true,
lib: {
entry: {
index: path.resolve(__dirname, 'index.ts'),
models: path.resolve(__dirname, 'models.ts'),
},
formats: ['cjs'],
fileName: (_format, entryName) => `${entryName}.js`,
},
},
plugins: [
cleanOutputDirs(),
peggyLoader(),
copyNodeRuntimeAssets(),
visualizer({ template: 'raw-data', filename: 'app/stats.json' }),
],
resolve: {
conditions: ['api'],
},
test: {
globals: true,
// e2e/ holds Playwright tests (yarn e2e), not vitest ones.
exclude: [...configDefaults.exclude, 'e2e/**'],
// Each test loads a budget file and runs all DB migrations, which can be
// slow on busy CI runners; the default 5s timeout is too tight and causes
// flaky timeouts (and a cascade of unhandled rejections from in-flight work
// continuing after teardown).
testTimeout: 20_000,
hookTimeout: 20_000,
onConsoleLog(log: string, type: 'stdout' | 'stderr'): boolean | void {
// print only console.error
return type === 'stderr';
},
maxWorkers: 2,
reporters: process.env.CI
? [
'default',
[
'junit',
{ outputFile: './test-results/junit.xml', suiteName: 'api' },
],
]
: ['default'],
},
});