Compare commits

...

2 Commits

Author SHA1 Message Date
Claude
7fe4a2f573 [AI] CLI: hide hidden categories by default in list commands
The `categories list` and `category-groups list` commands now exclude
hidden entries by default. Pass `--include-hidden` to include them, mirroring
the existing `--include-closed` flag for `accounts list`.

https://claude.ai/code/session_01DhYiicACsWb5NGHX71Wv4F
2026-05-05 22:27:50 +00:00
Matt Fiddaman
263358b5cf fix vrt update workflow (#7699)
* fix vrt update workflow

* note
2026-05-05 20:26:48 +00:00
6 changed files with 227 additions and 9 deletions

View File

@@ -65,6 +65,10 @@ jobs:
ref: ${{ steps.pr.outputs.head_sha }}
persist-credentials: false
- name: Trust workspace directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
shell: bash
- name: Set up environment
uses: ./.github/actions/setup
with:
@@ -87,9 +91,6 @@ jobs:
- name: Create patch with PNG changes only
id: create-patch
run: |
# Trust the repository directory (required for container environments)
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

View File

@@ -0,0 +1,192 @@
import * as api from '@actual-app/api';
import { Command } from 'commander';
import { printOutput } from '#output';
import { registerCategoriesCommand } from './categories';
import { registerCategoryGroupsCommand } from './category-groups';
vi.mock('@actual-app/api', () => ({
getCategories: vi.fn().mockResolvedValue([]),
createCategory: vi.fn().mockResolvedValue('new-id'),
updateCategory: vi.fn().mockResolvedValue(undefined),
deleteCategory: vi.fn().mockResolvedValue(undefined),
getCategoryGroups: vi.fn().mockResolvedValue([]),
createCategoryGroup: vi.fn().mockResolvedValue('new-group-id'),
updateCategoryGroup: vi.fn().mockResolvedValue(undefined),
deleteCategoryGroup: vi.fn().mockResolvedValue(undefined),
}));
vi.mock('#connection', () => ({
withConnection: vi.fn((_opts, fn) => fn()),
}));
vi.mock('#output', () => ({
printOutput: vi.fn(),
}));
function createProgram(): Command {
const program = new Command();
program.option('--format <format>');
program.option('--server-url <url>');
program.option('--password <pw>');
program.option('--session-token <token>');
program.option('--sync-id <id>');
program.option('--data-dir <dir>');
program.option('--verbose');
program.exitOverride();
registerCategoriesCommand(program);
registerCategoryGroupsCommand(program);
return program;
}
async function run(args: string[]) {
const program = createProgram();
await program.parseAsync(['node', 'test', ...args]);
}
describe('categories list', () => {
let stderrSpy: ReturnType<typeof vi.spyOn>;
let stdoutSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
vi.clearAllMocks();
stderrSpy = vi
.spyOn(process.stderr, 'write')
.mockImplementation(() => true);
stdoutSpy = vi
.spyOn(process.stdout, 'write')
.mockImplementation(() => true);
});
afterEach(() => {
stderrSpy.mockRestore();
stdoutSpy.mockRestore();
});
it('filters out hidden categories by default', async () => {
vi.mocked(api.getCategories).mockResolvedValue([
{ id: '1', name: 'Visible', group_id: 'g1', hidden: false },
{ id: '2', name: 'Hidden', group_id: 'g1', hidden: true },
]);
await run(['categories', 'list']);
expect(printOutput).toHaveBeenCalledWith(
[{ id: '1', name: 'Visible', group_id: 'g1', hidden: false }],
undefined,
);
});
it('includes hidden categories when --include-hidden is passed', async () => {
vi.mocked(api.getCategories).mockResolvedValue([
{ id: '1', name: 'Visible', group_id: 'g1', hidden: false },
{ id: '2', name: 'Hidden', group_id: 'g1', hidden: true },
]);
await run(['categories', 'list', '--include-hidden']);
expect(printOutput).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ id: '2', hidden: true }),
]),
undefined,
);
});
it('passes format option to printOutput', async () => {
vi.mocked(api.getCategories).mockResolvedValue([]);
await run(['--format', 'csv', 'categories', 'list']);
expect(printOutput).toHaveBeenCalledWith([], 'csv');
});
});
describe('category-groups list', () => {
let stderrSpy: ReturnType<typeof vi.spyOn>;
let stdoutSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
vi.clearAllMocks();
stderrSpy = vi
.spyOn(process.stderr, 'write')
.mockImplementation(() => true);
stdoutSpy = vi
.spyOn(process.stdout, 'write')
.mockImplementation(() => true);
});
afterEach(() => {
stderrSpy.mockRestore();
stdoutSpy.mockRestore();
});
it('filters out hidden groups and hidden child categories by default', async () => {
vi.mocked(api.getCategoryGroups).mockResolvedValue([
{
id: 'g1',
name: 'Visible Group',
is_income: false,
hidden: false,
categories: [
{ id: 'c1', name: 'Visible Cat', group_id: 'g1', hidden: false },
{ id: 'c2', name: 'Hidden Cat', group_id: 'g1', hidden: true },
],
},
{
id: 'g2',
name: 'Hidden Group',
is_income: false,
hidden: true,
categories: [],
},
]);
await run(['category-groups', 'list']);
expect(printOutput).toHaveBeenCalledWith(
[
{
id: 'g1',
name: 'Visible Group',
is_income: false,
hidden: false,
categories: [
{ id: 'c1', name: 'Visible Cat', group_id: 'g1', hidden: false },
],
},
],
undefined,
);
});
it('includes hidden groups and categories when --include-hidden is passed', async () => {
vi.mocked(api.getCategoryGroups).mockResolvedValue([
{
id: 'g1',
name: 'Visible Group',
is_income: false,
hidden: false,
categories: [
{ id: 'c2', name: 'Hidden Cat', group_id: 'g1', hidden: true },
],
},
{
id: 'g2',
name: 'Hidden Group',
is_income: false,
hidden: true,
categories: [],
},
]);
await run(['category-groups', 'list', '--include-hidden']);
const output = vi.mocked(printOutput).mock.calls[0][0] as Array<{
id: string;
}>;
expect(output).toHaveLength(2);
expect(output.map(g => g.id)).toEqual(['g1', 'g2']);
});
});

View File

@@ -12,13 +12,17 @@ export function registerCategoriesCommand(program: Command) {
categories
.command('list')
.description('List all categories')
.action(async () => {
.description('List categories (excludes hidden by default)')
.option('--include-hidden', 'Include hidden categories', false)
.action(async cmdOpts => {
const opts = program.opts();
await withConnection(
opts,
async () => {
const result = await api.getCategories();
const allCategories = await api.getCategories();
const result = allCategories.filter(
c => cmdOpts.includeHidden || !c.hidden,
);
printOutput(result, opts.format);
},
{ mutates: false },

View File

@@ -12,13 +12,22 @@ export function registerCategoryGroupsCommand(program: Command) {
groups
.command('list')
.description('List all category groups')
.action(async () => {
.description('List category groups (excludes hidden by default)')
.option('--include-hidden', 'Include hidden groups and categories', false)
.action(async cmdOpts => {
const opts = program.opts();
await withConnection(
opts,
async () => {
const result = await api.getCategoryGroups();
const allGroups = await api.getCategoryGroups();
const result = cmdOpts.includeHidden
? allGroups
: allGroups
.filter(g => !g.hidden)
.map(g => ({
...g,
categories: g.categories?.filter(c => !c.hidden),
}));
printOutput(result, opts.format);
},
{ mutates: false },

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [matt-fidd]
---
Fix VRT update workflow failing

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: []
---
CLI: `categories list` and `category-groups list` now exclude hidden entries by default. Pass `--include-hidden` to include them.