Skip to content

Commit

Permalink
feat: handle loadConfig by new safeLoadConfig
Browse files Browse the repository at this point in the history
It's refactored to pass zod parse result down to caller
  • Loading branch information
msudgh committed Jul 20, 2024
1 parent 91c4f36 commit b86b6c8
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 288 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This project is designed to improve the management of vaults in obsidian, to rem
- [ovm - Obsidian vaults manager](#ovm---obsidian-vaults-manager)
- [Usage](#usage)
- [Commands](#commands)
- [`ovm plugins init`](#ovm-plugins-init)
- [`ovm config init`](#ovm-config-init)
- [`ovm plugins install`](#ovm-plugins-install)
- [`ovm plugins prune`](#ovm-plugins-prune)
- [`ovm plugins uninstall`](#ovm-plugins-uninstall)
Expand All @@ -33,11 +33,11 @@ USAGE

## Commands

### `ovm plugins init`
### `ovm config init`

Configure an ovm.json config file in user's home dir.

- _Usage:_ `ovm help config`
- _Usage:_ `ovm help config init`
- _See code:_ [src/commands/config/init.ts](src/commands/config/init.ts)

### `ovm plugins install`
Expand Down
12 changes: 6 additions & 6 deletions src/commands/config/init.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { flush } from '@oclif/core'
import { ArgInput } from '@oclif/core/lib/parser'
import FactoryCommand, { CommonFlags, FactoryFlags } from '../../providers/command'
import { createDefaultConfig, loadConfig } from '../../providers/config'
import { createDefaultConfig, safeLoadConfig } from '../../providers/config'
import { logger } from '../../utils/logger'

const description = `Configure an ovm.json config file in user's home dir.`

Expand Down Expand Up @@ -42,20 +43,19 @@ export default class Init extends FactoryCommand {
this.flagsInterceptor(flags)

try {
const config = await loadConfig(flags.config)
const {data: config} = await safeLoadConfig()

if (config) {
this.log(`Config path: ${flags.config}`)
this.error('File already exists!')
logger.error('File already exists!', {config: flags.config})
process.exit(1)
}
} catch (error) {
const typedError = error as Error

if (typedError && typedError.message === 'Config file not found') {
try {
await createDefaultConfig(flags.config)
this.log(`Config path: ${flags.config}`)
this.log('File created!')
logger.info('Config file created', {config: flags.config})
} catch (error) {
this.handleError(error)
}
Expand Down
190 changes: 106 additions & 84 deletions src/commands/plugins/install.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,56 @@
import { Flags, flush, handle } from '@oclif/core'
import { ArgInput } from '@oclif/core/lib/parser'
import { eachSeries } from 'async'
import { installPluginFromGithub, isPluginInstalled, Vault } from 'obsidian-utils'
import FactoryCommand, { FactoryFlags } from '../../providers/command'
import { Config, loadConfig } from '../../providers/config'
import { findPluginInRegistry, handleExceedRateLimitError } from '../../providers/github'
import { modifyCommunityPlugins } from '../../services/plugins'
import { vaultsSelector } from '../../services/vaults'
import { logger } from '../../utils/logger'

const description = `Install plugins in specified vaults.`
import { Flags, flush, handle } from "@oclif/core";
import { ArgInput } from "@oclif/core/lib/parser";
import { eachSeries } from "async";
import {
installPluginFromGithub,
isPluginInstalled,
Vault,
} from "obsidian-utils";
import FactoryCommand, { FactoryFlags } from "../../providers/command";
import { Config, safeLoadConfig } from "../../providers/config";
import {
findPluginInRegistry,
handleExceedRateLimitError,
} from "../../providers/github";
import { modifyCommunityPlugins } from "../../services/plugins";
import { vaultsSelector } from "../../services/vaults";
import { logger } from "../../utils/logger";

interface InstallFlags {
path: string
enable: boolean
path: string;
enable: boolean;
}

interface InstallPluginVaultOpts {
vault: Vault
config: Config
vault: Vault;
config: Config;
}

/**
* Install command installs specified plugins in vaults.
*/
export default class Install extends FactoryCommand {
static readonly aliases = ['pi', 'plugins:install']
static override readonly description = description
static readonly aliases = ["pi", "plugins:install"];
static override readonly description = `Install plugins in specified vaults.`;
static override readonly examples = [
'<%= config.bin %> <%= command.id %> --path=/path/to/vaults',
'<%= config.bin %> <%= command.id %> --path=/path/to/vaults/*/.obsidian',
'<%= config.bin %> <%= command.id %> --path=/path/to/vaults/**/.obsidian',
]
"<%= config.bin %> <%= command.id %> --path=/path/to/vaults",
"<%= config.bin %> <%= command.id %> --path=/path/to/vaults/*/.obsidian",
"<%= config.bin %> <%= command.id %> --path=/path/to/vaults/**/.obsidian",
];
static override readonly flags = {
path: Flags.string({
char: 'p',
char: "p",
description:
'Path or Glob pattern of vaults to install plugins. Default: reads from Obsidian config per environment.',
default: '',
"Path or Glob pattern of vaults to install plugins. (default: detects vaults from Obsidian configuration)",
default: "",
}),
enable: Flags.boolean({
char: 'e',
description: 'Enable the installed plugins',
char: "e",
description: "Enable all chosen plugins",
default: false,
}),
...this.commonFlags,
}
};

/**
* Executes the command.
Expand All @@ -54,12 +59,12 @@ export default class Install extends FactoryCommand {
*/
public async run() {
try {
const {args, flags} = await this.parse(Install)
await this.action(args, flags)
const { args, flags } = await this.parse(Install);
await this.action(args, this.flagsInterceptor(flags));
} catch (error) {
this.handleError(error)
this.handleError(error);
} finally {
flush()
flush();
}
}

Expand All @@ -70,66 +75,83 @@ export default class Install extends FactoryCommand {
* @param {FactoryFlags<InstallFlags>} flags - The flags passed to the command.
* @returns {Promise<void>}
*/
private async action(args: ArgInput, flags: FactoryFlags<InstallFlags>): Promise<void> {
this.flagsInterceptor(flags)

const {path, enable} = flags
const vaults = await this.loadVaults(path)
const selectedVaults = await vaultsSelector(vaults)

try {
const config = (await loadConfig()) as Config
const vaultsWithConfig = selectedVaults.map((vault) => ({vault, config}))
const installVaultIterator = async (opts: InstallPluginVaultOpts) => {
const {vault, config} = opts
logger.debug(`Install plugins for vault`, {vault})
const installedPlugins = []
const failedPlugins = []
private async action(
args: ArgInput,
flags: FactoryFlags<InstallFlags>,
): Promise<void> {
const { path, enable } = flags;
const {success: loadConfigSuccess, data: config, error: loadConfigError} = await safeLoadConfig()

if (!loadConfigSuccess) {
logger.error('Failed to load config', {error: loadConfigError})
process.exit(1)
}

for (const stagePlugin of config.plugins) {
const childLogger = logger.child({stagePlugin, vault})
const vaults = await this.loadVaults(path);
const selectedVaults = await vaultsSelector(vaults);
const vaultsWithConfig = selectedVaults.map((vault) => ({
vault,
config,
}));
const installVaultIterator = async (opts: InstallPluginVaultOpts) => {
const { vault, config } = opts;
logger.debug(`Install plugins for vault`, { vault });
const installedPlugins = [];
const failedPlugins = [];

for (const stagePlugin of config.plugins) {
const childLogger = logger.child({ stagePlugin, vault });

const pluginInRegistry = await findPluginInRegistry(stagePlugin.id);
if (!pluginInRegistry) {
throw new Error(`Plugin ${stagePlugin.id} not found in registry`);
}

const pluginInRegistry = await findPluginInRegistry(stagePlugin.id)
if (!pluginInRegistry) {
throw new Error(`Plugin ${stagePlugin.id} not found in registry`)
}
if (await isPluginInstalled(pluginInRegistry.id, vault.path)) {
childLogger.info(`Plugin already installed`);
continue;
}

if (await isPluginInstalled(pluginInRegistry.id, vault.path)) {
childLogger.info(`Plugin already installed`)
continue
try {
await installPluginFromGithub(
pluginInRegistry.repo,
stagePlugin.version,
vault.path,
);
installedPlugins.push({
repo: pluginInRegistry.repo,
version: stagePlugin.version,
});

if (enable) {
// Enable the plugin
await modifyCommunityPlugins(stagePlugin, vault.path, "enable");
}

try {
await installPluginFromGithub(pluginInRegistry.repo, stagePlugin.version, vault.path)
installedPlugins.push({repo: pluginInRegistry.repo, version: stagePlugin.version})

if (enable) {
// Enable the plugin
await modifyCommunityPlugins(stagePlugin, vault.path, 'enable')
}

childLogger.debug(`Installed plugin`)
} catch (error) {
failedPlugins.push({repo: pluginInRegistry.repo, version: stagePlugin.version})
handleExceedRateLimitError(error)
childLogger.error(`Failed to install plugin`, {error})
}
childLogger.debug(`Installed plugin`);
} catch (error) {
failedPlugins.push({
repo: pluginInRegistry.repo,
version: stagePlugin.version,
});
handleExceedRateLimitError(error);
childLogger.error(`Failed to install plugin`, { error });
}
}

if (installedPlugins.length > 0) {
logger.info(`Installed ${installedPlugins.length} plugins`, {vault})
}
if (installedPlugins.length > 0) {
logger.info(`Installed ${installedPlugins.length} plugins`, {
vault,
});
}

return {installedPlugins, failedPlugins}
return { installedPlugins, failedPlugins };
};
eachSeries(vaultsWithConfig, installVaultIterator, (error) => {
if (error) {
logger.debug("Error installing plugins", { error });
handle(error);
}
eachSeries(vaultsWithConfig, installVaultIterator, (error) => {
if (error) {
logger.debug('Error installing plugins', {error})
handle(error)
}
})
} catch (error) {
this.handleError(error)
}
});
}
}
71 changes: 35 additions & 36 deletions src/commands/plugins/prune.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {Flags, flush, handle} from '@oclif/core'
import {ArgInput} from '@oclif/core/lib/parser'
import {eachSeries} from 'async'
import {Vault} from 'obsidian-utils'
import FactoryCommand, {FactoryFlags} from '../../providers/command'
import {Config, loadConfig} from '../../providers/config'
import {listInstalledPlugins, removePluginDir} from '../../services/plugins'
import {vaultsSelector} from '../../services/vaults'
import {logger} from '../../utils/logger'
import { Flags, flush, handle } from '@oclif/core'
import { ArgInput } from '@oclif/core/lib/parser'
import { eachSeries } from 'async'
import { Vault } from 'obsidian-utils'
import FactoryCommand, { FactoryFlags } from '../../providers/command'
import { Config, safeLoadConfig } from '../../providers/config'
import { listInstalledPlugins, removePluginDir } from '../../services/plugins'
import { vaultsSelector } from '../../services/vaults'
import { logger } from '../../utils/logger'

const description = `Prune plugins from specified vaults.`

Expand Down Expand Up @@ -48,7 +48,7 @@ export default class Prune extends FactoryCommand {
public async run() {
try {
const {args, flags} = await this.parse(Prune)
await this.action(args, flags)
await this.action(args, this.flagsInterceptor(flags))
} catch (error) {
this.handleError(error)
} finally {
Expand All @@ -64,38 +64,37 @@ export default class Prune extends FactoryCommand {
* @returns {Promise<void>}
*/
private async action(args: ArgInput, flags: FactoryFlags<PruneFlags>): Promise<void> {
this.flagsInterceptor(flags)

const {path} = flags
const vaults = await this.loadVaults(path)
const selectedVaults = await vaultsSelector(vaults)
const {success: loadConfigSuccess, data: config, error: loadConfigError} = await safeLoadConfig()

try {
const config = (await loadConfig()) as Config
const vaultsWithConfig = selectedVaults.map((vault) => ({vault, config}))
const prunePluginsIterator = async (opts: PrunePluginVaultOpts) => {
const {vault, config} = opts
const childLogger = logger.child({vault})
const installedPlugins = await listInstalledPlugins(vault.path)
const referencedPlugins = config.plugins.map(({id}) => id)
const toBePruned = installedPlugins.filter(({id}) => !referencedPlugins.includes(id))
if (!loadConfigSuccess) {
logger.error('Failed to load config', {error: loadConfigError})
process.exit(1)
}

for (const plugin of toBePruned) {
childLogger.debug(`Pruning plugin`, {plugin})
await removePluginDir(plugin.id, vault.path)
}
const vaults = await this.loadVaults(path)
const selectedVaults = await vaultsSelector(vaults)
const vaultsWithConfig = selectedVaults.map((vault) => ({vault, config}))
const prunePluginsIterator = async (opts: PrunePluginVaultOpts) => {
const {vault, config} = opts
const childLogger = logger.child({vault})
const installedPlugins = await listInstalledPlugins(vault.path)
const referencedPlugins = config.plugins.map(({id}) => id)
const toBePruned = installedPlugins.filter(({id}) => !referencedPlugins.includes(id))

childLogger.info(`Pruned ${toBePruned.length} plugins`)
for (const plugin of toBePruned) {
childLogger.debug(`Pruning plugin`, {plugin})
await removePluginDir(plugin.id, vault.path)
}

eachSeries(vaultsWithConfig, prunePluginsIterator, (error) => {
if (error) {
logger.debug('Error pruning plugins', {error})
handle(error)
}
})
} catch (error) {
this.handleError(error)
childLogger.info(`Pruned ${toBePruned.length} plugins`)
}

eachSeries(vaultsWithConfig, prunePluginsIterator, (error) => {
if (error) {
logger.debug('Error pruning plugins', {error})
handle(error)
}
})
}
}
Loading

0 comments on commit b86b6c8

Please sign in to comment.