feat: allow configurable config dir
This commit is contained in:
parent
19f83f3c79
commit
7c6ee25f89
9 changed files with 46 additions and 31 deletions
|
@ -58,7 +58,7 @@
|
|||
"format": "prettier --check .",
|
||||
"format:fix": "prettier --write .",
|
||||
"check": "tsc --noEmit",
|
||||
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config test/e2e/jest-e2e.json --runInBand --verbose"
|
||||
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config test/e2e/jest-e2e.json --runInBand"
|
||||
},
|
||||
"jest": {
|
||||
"clearMocks": true,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { SessionService } from '../services/session.service';
|
|||
import { LoginError } from '../cores/errors/login-error';
|
||||
import { exit } from 'node:process';
|
||||
import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api';
|
||||
import { BaseOptionsDto } from 'src/cores/dto/base-options-dto';
|
||||
|
||||
export abstract class BaseCommand {
|
||||
protected sessionService!: SessionService;
|
||||
|
@ -10,8 +11,11 @@ export abstract class BaseCommand {
|
|||
protected user!: UserResponseDto;
|
||||
protected serverVersion!: ServerVersionResponseDto;
|
||||
|
||||
constructor() {
|
||||
this.sessionService = new SessionService();
|
||||
constructor(options: BaseOptionsDto) {
|
||||
if (!options.config) {
|
||||
throw new Error('Config directory is required');
|
||||
}
|
||||
this.sessionService = new SessionService(options.config);
|
||||
}
|
||||
|
||||
public async connect(): Promise<void> {
|
||||
|
|
3
cli/src/cores/dto/base-options-dto.ts
Normal file
3
cli/src/cores/dto/base-options-dto.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export class BaseOptionsDto {
|
||||
config?: string;
|
||||
}
|
|
@ -1,12 +1,20 @@
|
|||
#! /usr/bin/env node
|
||||
|
||||
import { program, Option } from 'commander';
|
||||
import { Option, Command } from 'commander';
|
||||
import Upload from './commands/upload';
|
||||
import ServerInfo from './commands/server-info';
|
||||
import LoginKey from './commands/login/key';
|
||||
import Logout from './commands/logout';
|
||||
import path from 'node:path';
|
||||
import os from 'os';
|
||||
|
||||
program.name('immich').description('Immich command line interface');
|
||||
const userHomeDir = os.homedir();
|
||||
const configDir = path.join(userHomeDir, '.config/immich/');
|
||||
|
||||
const program = new Command();
|
||||
program.name('immich');
|
||||
program.description('Immich command line interface');
|
||||
program.addOption(new Option('-d, --config', 'Configuration directory').env('IMMICH_CONFIG_DIR').default(configDir));
|
||||
|
||||
program
|
||||
.command('upload')
|
||||
|
@ -29,14 +37,14 @@ program
|
|||
.argument('[paths...]', 'One or more paths to assets to be uploaded')
|
||||
.action(async (paths, options) => {
|
||||
options.exclusionPatterns = options.ignore;
|
||||
await new Upload().run(paths, options);
|
||||
await new Upload(program.opts()).run(paths, options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('server-info')
|
||||
.description('Display server information')
|
||||
.action(async () => {
|
||||
await new ServerInfo().run();
|
||||
await new ServerInfo(program.opts()).run();
|
||||
});
|
||||
|
||||
program
|
||||
|
@ -45,14 +53,14 @@ program
|
|||
.argument('[instanceUrl]')
|
||||
.argument('[apiKey]')
|
||||
.action(async (paths, options) => {
|
||||
await new LoginKey().run(paths, options);
|
||||
await new LoginKey(program.opts()).run(paths, options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('logout')
|
||||
.description('Remove stored credentials')
|
||||
.action(async () => {
|
||||
await new Logout().run();
|
||||
await new Logout(program.opts()).run();
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
|
|
@ -10,21 +10,9 @@ export class SessionService {
|
|||
readonly authPath!: string;
|
||||
private api!: ImmichApi;
|
||||
|
||||
constructor(configDir?: string) {
|
||||
if (!configDir) {
|
||||
configDir = process.env.IMMICH_CONFIG_DIR;
|
||||
|
||||
if (!configDir) {
|
||||
const userHomeDir = os.homedir();
|
||||
this.configDir = path.join(userHomeDir, '.config/immich/');
|
||||
} else {
|
||||
this.configDir = configDir;
|
||||
}
|
||||
} else {
|
||||
this.configDir = configDir;
|
||||
}
|
||||
|
||||
this.authPath = path.join(this.configDir, 'auth.yml');
|
||||
constructor(configDir: string) {
|
||||
this.configDir = configDir;
|
||||
this.authPath = path.join(configDir, 'auth.yml');
|
||||
}
|
||||
|
||||
public async connect(): Promise<ImmichApi> {
|
||||
|
|
5
cli/test/cli-test-utils.ts
Normal file
5
cli/test/cli-test-utils.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { BaseOptionsDto } from 'src/cores/dto/base-options-dto';
|
||||
|
||||
export const CLI_BASE_OPTIONS: BaseOptionsDto = { config: '/tmp/immich/' };
|
||||
|
||||
export const spyOnConsole = () => jest.spyOn(console, 'log').mockImplementation();
|
|
@ -4,13 +4,14 @@ import { LoginResponseDto } from 'src/api/open-api';
|
|||
import { APIKeyCreateResponseDto } from '@app/domain';
|
||||
import LoginKey from 'src/commands/login/key';
|
||||
import { LoginError } from 'src/cores/errors/login-error';
|
||||
import { CLI_BASE_OPTIONS, spyOnConsole } from 'test/cli-test-utils';
|
||||
|
||||
describe(`login-key (e2e)`, () => {
|
||||
let server: any;
|
||||
let admin: LoginResponseDto;
|
||||
let apiKey: APIKeyCreateResponseDto;
|
||||
let instanceUrl: string;
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
const consoleSpy = spyOnConsole();
|
||||
|
||||
beforeAll(async () => {
|
||||
server = (await testApp.create({ jobs: true })).getHttpServer();
|
||||
|
@ -36,12 +37,12 @@ describe(`login-key (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should error when providing an invalid API key', async () => {
|
||||
expect(async () => await new LoginKey().run(instanceUrl, 'invalid')).rejects.toThrow(
|
||||
expect(async () => await new LoginKey(CLI_BASE_OPTIONS).run(instanceUrl, 'invalid')).rejects.toThrow(
|
||||
new LoginError(`Failed to connect to server ${instanceUrl}: Request failed with status code 401`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log in when providing the correct API key', async () => {
|
||||
await new LoginKey().run(instanceUrl, apiKey.secret);
|
||||
await new LoginKey(CLI_BASE_OPTIONS).run(instanceUrl, apiKey.secret);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,12 +3,13 @@ import { restoreTempFolder, testApp } from 'immich/test/test-utils';
|
|||
import { LoginResponseDto } from 'src/api/open-api';
|
||||
import ServerInfo from 'src/commands/server-info';
|
||||
import { APIKeyCreateResponseDto } from '@app/domain';
|
||||
import { CLI_BASE_OPTIONS, spyOnConsole } from 'test/cli-test-utils';
|
||||
|
||||
describe(`server-info (e2e)`, () => {
|
||||
let server: any;
|
||||
let admin: LoginResponseDto;
|
||||
let apiKey: APIKeyCreateResponseDto;
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
const consoleSpy = spyOnConsole();
|
||||
|
||||
beforeAll(async () => {
|
||||
server = (await testApp.create({ jobs: true })).getHttpServer();
|
||||
|
@ -29,7 +30,7 @@ describe(`server-info (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should show server version', async () => {
|
||||
await new ServerInfo().run();
|
||||
await new ServerInfo(CLI_BASE_OPTIONS).run();
|
||||
|
||||
expect(consoleSpy.mock.calls).toEqual([
|
||||
[expect.stringMatching(new RegExp('Server is running version \\d+.\\d+.\\d+'))],
|
||||
|
|
|
@ -3,11 +3,13 @@ import { IMMICH_TEST_ASSET_PATH, restoreTempFolder, testApp } from 'immich/test/
|
|||
import { LoginResponseDto } from 'src/api/open-api';
|
||||
import Upload from 'src/commands/upload';
|
||||
import { APIKeyCreateResponseDto } from '@app/domain';
|
||||
import { CLI_BASE_OPTIONS, spyOnConsole } from 'test/cli-test-utils';
|
||||
|
||||
describe(`upload (e2e)`, () => {
|
||||
let server: any;
|
||||
let admin: LoginResponseDto;
|
||||
let apiKey: APIKeyCreateResponseDto;
|
||||
spyOnConsole();
|
||||
|
||||
beforeAll(async () => {
|
||||
server = (await testApp.create({ jobs: true })).getHttpServer();
|
||||
|
@ -28,13 +30,16 @@ describe(`upload (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should upload a folder recursively', async () => {
|
||||
await new Upload().run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { recursive: true });
|
||||
await new Upload(CLI_BASE_OPTIONS).run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { recursive: true });
|
||||
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
||||
expect(assets.length).toBeGreaterThan(4);
|
||||
});
|
||||
|
||||
it('should create album from folder name', async () => {
|
||||
await new Upload().run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { recursive: true, album: true });
|
||||
await new Upload(CLI_BASE_OPTIONS).run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], {
|
||||
recursive: true,
|
||||
album: true,
|
||||
});
|
||||
const albums = await api.albumApi.getAllAlbums(server, admin.accessToken);
|
||||
expect(albums.length).toEqual(1);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue