media | commons - sal

This commit is contained in:
babayaga 2025-08-11 19:26:32 +02:00
parent c7f92ece52
commit 9bafb02aa9
43 changed files with 7193 additions and 85 deletions

1
packages/.gitignore vendored
View File

@ -3,3 +3,4 @@
*.log
.DS_Store
part-registry
nexe

2
packages/commons/dist/_cli.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export declare const defaults: () => void;
export declare const sanitize: (argv: any) => any;

16
packages/commons/dist/_cli.js vendored Normal file
View File

@ -0,0 +1,16 @@
export const defaults = () => {
const DefaultCommand = 'salamander';
if (process.argv.length === 2) {
process.argv.push(DefaultCommand);
}
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection, reason: ', reason);
});
};
export const sanitize = (argv) => {
return {
...argv,
logLevel: argv.logLevel || 'info'
};
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2NsaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9fY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxHQUFHLEVBQUU7SUFDekIsTUFBTSxjQUFjLEdBQUcsWUFBWSxDQUFDO0lBQ3BDLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDNUIsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUNELE9BQU8sQ0FBQyxFQUFFLENBQUMsb0JBQW9CLEVBQUUsQ0FBQyxNQUFjLEVBQUUsRUFBRTtRQUNoRCxPQUFPLENBQUMsS0FBSyxDQUFDLCtCQUErQixFQUFFLE1BQU0sQ0FBQyxDQUFBO0lBQzFELENBQUMsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFBO0FBRUQsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHLENBQUMsSUFBUyxFQUFPLEVBQUU7SUFDdkMsT0FBTztRQUNILEdBQUcsSUFBSTtRQUNQLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxJQUFJLE1BQU07S0FDcEMsQ0FBQTtBQUNMLENBQUMsQ0FBQSJ9

1
packages/commons/dist/cli.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare const cli: any;

4
packages/commons/dist/cli.js vendored Normal file
View File

@ -0,0 +1,4 @@
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
export const cli = yargs(hideBin(process.argv));
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NsaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssTUFBTSxPQUFPLENBQUE7QUFDekIsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGVBQWUsQ0FBQTtBQUV2QyxNQUFNLENBQUMsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQSJ9

View File

@ -0,0 +1,6 @@
import * as CLI from 'yargs';
export declare const defaultOptions: (yargs: CLI.Argv) => any;
export declare const command = "register-commands";
export declare const desc = "Register all pm-media commands in Salamander menu";
export declare const builder: (yargs: CLI.Argv) => any;
export declare function handler(argv: CLI.Arguments): Promise<void>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
import * as CLI from 'yargs';
export declare const defaultOptions: (yargs: CLI.Argv) => any;
export declare const command = "salamander";
export declare const desc = "Generate Salamander file manager menu entries from JSON configuration";
export declare const builder: (yargs: CLI.Argv) => any;
export declare function handler(argv: CLI.Arguments): Promise<void>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,132 @@
export interface SalamanderMenuItem {
name: string;
command?: string;
arguments?: string;
initialDirectory?: string;
executeThoughShell?: boolean;
closeShellWindow?: boolean;
openShellWindow?: boolean;
icon?: string;
showInToolbar?: boolean;
type?: 'command' | 'submenu' | 'submenu-end';
children?: SalamanderMenuItem[];
}
export interface SalamanderMenuConfig {
baseKey: string;
startIndex: number;
items: SalamanderMenuItem[];
}
export interface RegistryEntry {
key: string;
values: Record<string, string | number>;
}
export declare class SalamanderMenuGenerator {
private config;
private baseKey;
constructor(config: SalamanderMenuConfig);
/**
* Parse existing registry file to find the highest menu index
*/
static parseExistingRegistry(registryPath: string): number;
/**
* Generate registry entries from menu configuration
*/
generateRegistryEntries(): RegistryEntry[];
/**
* Convert menu item to registry values
*/
private itemToRegistryValues;
/**
* Escape arguments for registry format
*/
private escapeArguments;
/**
* Generate registry file content
*/
generateRegistryFile(): string;
/**
* Save registry file to disk
*/
saveRegistryFile(outputPath: string): void;
/**
* Load menu configuration from JSON file
*/
static loadFromJson(jsonPath: string): SalamanderMenuConfig;
/**
* Find insertion point in existing registry
*/
static findInsertionPoint(registryPath: string, groupName?: string): number;
}
/**
* Windows Registry helper for direct registry operations using regedit package
*/
export declare class WindowsRegistry {
private static isWindows;
private static ensureRegedit;
/**
* Read all User Menu entries from registry
*/
static readUserMenuEntries(baseKey?: string): Promise<Record<string, any>>;
/**
* Read specific menu entry by index
*/
static readMenuEntry(index: number, baseKey?: string): Promise<Record<string, any>>;
/**
* Get all existing menu indices
*/
static getExistingMenuIndices(baseKey?: string): Promise<number[]>;
/**
* Get next available menu index
*/
static getNextMenuIndex(baseKey?: string): Promise<number>;
/**
* Find insertion point for a specific group
*/
static findGroupInsertionPoint(groupName: string, baseKey?: string): Promise<number>;
/**
* Write registry values for a menu entry
*/
static writeMenuEntry(index: number, values: Record<string, any>, baseKey?: string): Promise<void>;
/**
* Delete a menu entry
*/
static deleteMenuEntry(index: number, baseKey?: string): Promise<void>;
/**
* List all menu entries with their details
*/
static listAllMenuEntries(baseKey?: string): Promise<Array<{
index: number;
name: string;
type: string;
command?: string;
arguments?: string;
}>>;
}
/**
* Extended Salamander Menu Generator with direct registry support
*/
export declare class SalamanderMenuGeneratorRegistry extends SalamanderMenuGenerator {
/**
* Apply menu configuration directly to Windows registry
*/
applyToRegistry(): Promise<void>;
/**
* Remove menu entries from Windows registry
*/
removeFromRegistry(): Promise<void>;
/**
* Auto-detect insertion point from Windows registry
*/
static autoDetectInsertionPoint(groupName?: string): Promise<number>;
/**
* List current menu entries from registry
*/
static listCurrentMenuEntries(): Promise<Array<{
index: number;
name: string;
type: string;
command?: string;
arguments?: string;
}>>;
}
export default SalamanderMenuGenerator;

File diff suppressed because one or more lines are too long

2
packages/commons/dist/main.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
import './commands/register-commands.js';

14
packages/commons/dist/main.js vendored Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env node
import { defaults } from './_cli.js';
defaults();
import { cli } from './cli.js';
import './commands/register-commands.js';
const argv = cli.argv;
if (argv.h || argv.help) {
cli.showHelp();
process.exit();
}
else if (argv.v || argv.version) {
process.exit();
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9tYWluLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQUMsUUFBUSxFQUFFLENBQUE7QUFFaEQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLFVBQVUsQ0FBQTtBQUU5QixPQUFPLGlDQUFpQyxDQUFBO0FBRXhDLE1BQU0sSUFBSSxHQUFRLEdBQUcsQ0FBQyxJQUFJLENBQUM7QUFFM0IsSUFBSSxJQUFJLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN0QixHQUFHLENBQUMsUUFBUSxFQUFFLENBQUM7SUFDZixPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDbkIsQ0FBQztLQUFNLElBQUksSUFBSSxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDaEMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO0FBQ25CLENBQUMifQ==

View File

@ -3,6 +3,9 @@
"version": "0.2.6",
"license": "BSD",
"type": "module",
"bin": {
"pm-cli": "./dist/main.js"
},
"publishConfig": {
"access": "public"
},
@ -72,6 +75,7 @@
"normalize-url": "^8.0.1",
"p-map": "^7.0.3",
"p-throttle": "^4.1.1",
"regedit": "^5.1.4",
"tslog": "^3.3.3",
"tsup": "^2.0.3",
"yargs": "^17.7.2",
@ -86,7 +90,7 @@
"scripts": {
"test": "tsc && mocha build/test",
"buildtsc": "tsc -p . --declaration",
"build": "tsup",
"build": "tsc -p . --declaration",
"start": "node build/index.js",
"typings": "tsc -p . --declaration",
"dev": "tsc -p . --declaration -w"

View File

@ -0,0 +1,8 @@
{
"ls-test": {
"name": "List Directory to File",
"command": "cmd",
"args": "/c dir \"$(FullPath)\" > \"$(FullPath)\\dir.txt\"",
"description": "List directory contents and save to dir.txt"
}
}

View File

@ -0,0 +1,16 @@
export const defaults = () => {
const DefaultCommand = 'salamander';
if (process.argv.length === 2) {
process.argv.push(DefaultCommand);
}
process.on('unhandledRejection', (reason: string) => {
console.error('Unhandled rejection, reason: ', reason)
})
}
export const sanitize = (argv: any): any => {
return {
...argv,
logLevel: argv.logLevel || 'info'
}
}

View File

@ -0,0 +1,4 @@
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
export const cli = yargs(hideBin(process.argv))

View File

@ -0,0 +1,252 @@
import * as CLI from 'yargs'
import * as fs from 'fs'
import * as path from 'path'
import { logger } from '../index.js'
import { cli } from '../cli.js'
import { defaults } from '../_cli.js'
import { WindowsRegistry } from '../lib/salamander/index.js'
export const defaultOptions = (yargs: CLI.Argv) => {
return yargs.option('group', {
describe: 'Group name to register commands under',
type: 'string',
default: 'Commons'
}).option('commands', {
describe: 'Path to JSON file with command mappings',
type: 'string',
default: './salamand.json'
}).option('dry', {
default: false,
describe: 'Show what would be registered without actually registering',
type: 'boolean'
}).option('force', {
default: false,
describe: 'Force register even if command already exists',
type: 'boolean'
}).option('logLevel', {
describe: 'Log level : warn, info, debug, error',
type: 'string',
default: 'info'
})
}
export const command = 'register-commands'
export const desc = 'Register all pm-media commands in Salamander menu'
export const builder = defaultOptions
interface CommandInfo {
name: string
command: string
args: string
description: string
}
// Default command mappings - can be overridden by JSON file
const DEFAULT_COMMAND_MAPPINGS: Record<string, CommandInfo> = {
}
/**
* Load command mappings from JSON file or use defaults
*/
function loadCommandMappings(configPath: string): Record<string, CommandInfo> {
try {
if (fs.existsSync(configPath)) {
const configContent = fs.readFileSync(configPath, 'utf8')
const config = JSON.parse(configContent)
logger.info(`Loaded command mappings from: ${configPath}`)
return config
} else {
logger.info(`Config file not found (${configPath}), using default mappings`)
return DEFAULT_COMMAND_MAPPINGS
}
} catch (error) {
logger.warn(`Failed to load config file (${configPath}):`, error)
logger.info('Using default command mappings')
return DEFAULT_COMMAND_MAPPINGS
}
}
async function getAvailableCommands(commandMappings: Record<string, CommandInfo>): Promise<string[]> {
const commandsDir = path.join(process.cwd(), 'src', 'commands')
const files = fs.readdirSync(commandsDir)
// Get commands from actual files
const fileBasedCommands = files
.filter(file => file.endsWith('.ts') &&
file !== 'salamander.ts' &&
file !== 'register-commands.ts')
.map(file => file.replace('.ts', ''))
.filter(cmd => commandMappings[cmd])
// Get all commands from mappings (includes custom commands like resize-square)
const allMappingCommands = Object.keys(commandMappings)
// Combine and deduplicate
const allCommands = [...new Set([...fileBasedCommands, ...allMappingCommands])]
return allCommands
}
export async function handler(argv: CLI.Arguments) {
defaults()
logger.settings.minLevel = argv.logLevel as any
const options = {
group: argv.group as string,
commands: argv.commands as string,
dry: argv.dry as boolean,
force: argv.force as boolean
}
try {
// Load command mappings from file or use defaults
const commandMappings = loadCommandMappings(options.commands)
logger.info('Scanning available built-in commands...')
const availableCommands = await getAvailableCommands(commandMappings)
logger.info(`Found ${availableCommands.length} commands: ${availableCommands.join(', ')}`)
if (options.dry) {
logger.info('\n=== DRY RUN - Commands that would be registered ===')
for (const cmdName of availableCommands) {
const cmdInfo = commandMappings[cmdName]
logger.info(`\nCommand: ${cmdName}`)
logger.info(` Name: ${cmdInfo.name}`)
logger.info(` Args: ${cmdInfo.args}`)
logger.info(` Group: ${options.group}`)
}
return
}
// Check which commands already exist
logger.info('Checking existing registry entries...')
const existingEntries = await WindowsRegistry.listAllMenuEntries()
const existingCommands = existingEntries
.filter(entry => entry.type === 'command') // Only commands, not submenus
logger.info(`Found ${existingCommands.length} existing command entries`)
let registeredCount = 0
let skippedCount = 0
// Check if the target group exists, create it if it doesn't
const groupExists = existingEntries.some(entry =>
entry.type === 'submenu' && entry.name === options.group
)
let groupInsertionPoint: number
if (!groupExists) {
logger.info(`Creating group: ${options.group}`)
// Get the starting index for the new submenu
groupInsertionPoint = await WindowsRegistry.getNextMenuIndex()
// Create the group submenu start
const groupStartValues = {
'Item Name': options.group,
'Command': '',
'Arguments': '',
'Initial Directory': '',
'Execute Through Shell': 'dword:00000000',
'Close Shell Window': 'dword:00000000',
'Open Shell Window': 'dword:00000000',
'Icon': '',
'Type': 'dword:00000001',
'Show In Toolbar': 'dword:00000001'
}
await WindowsRegistry.writeMenuEntry(groupInsertionPoint, groupStartValues)
logger.info(`✓ Created group start: ${options.group} at index ${groupInsertionPoint}`)
// Increment for the first command slot
groupInsertionPoint++
} else {
logger.info(`Group '${options.group}' already exists`)
groupInsertionPoint = await WindowsRegistry.findGroupInsertionPoint(options.group)
}
for (const cmdName of availableCommands) {
const cmdInfo = commandMappings[cmdName]
// Check if command already exists - look for exact name AND command match
const exists = existingCommands.some(entry => {
const nameMatch = entry.name === cmdInfo.name
const commandMatch = entry.command === cmdInfo.command
const argsMatch = entry.arguments === cmdInfo.args
// Consider it a duplicate if name and command match (even if args differ slightly)
return nameMatch && commandMatch
})
if (exists && !options.force) {
logger.info(`Skipping '${cmdInfo.name}' - already exists (use --force to override)`)
skippedCount++
continue
}
logger.info(`Registering: ${cmdInfo.name}`)
try {
// Build registry values for the command
const values = {
'Item Name': cmdInfo.name,
'Command': cmdInfo.command,
'Arguments': cmdInfo.args,
'Initial Directory': '$(FullPath)',
'Execute Through Shell': 'dword:00000001',
'Close Shell Window': 'dword:00000001',
'Open Shell Window': 'dword:00000001',
'Icon': '',
'Type': 'dword:00000000',
'Show In Toolbar': 'dword:00000001'
}
await WindowsRegistry.writeMenuEntry(groupInsertionPoint, values)
registeredCount++
logger.info(`✓ Registered: ${cmdInfo.name} at index ${groupInsertionPoint}`)
// Increment insertion point for next command to insert them sequentially within the group
groupInsertionPoint++
} catch (error) {
logger.error(`Failed to register '${cmdInfo.name}':`, error)
}
}
// If we created a new group and registered commands, add the submenu end
if (!groupExists && registeredCount > 0) {
const groupEndValues = {
'Item Name': '(Submenu End)',
'Command': '',
'Arguments': '',
'Initial Directory': '',
'Execute Through Shell': 'dword:00000000',
'Close Shell Window': 'dword:00000000',
'Open Shell Window': 'dword:00000000',
'Icon': '',
'Type': 'dword:00000002',
'Show In Toolbar': 'dword:00000000'
}
await WindowsRegistry.writeMenuEntry(groupInsertionPoint, groupEndValues)
logger.info(`✓ Created group end at index ${groupInsertionPoint}`)
}
logger.info(`\n=== Registration Complete ===`)
logger.info(`Registered: ${registeredCount} commands`)
logger.info(`Skipped: ${skippedCount} commands`)
logger.info(`Total available: ${availableCommands.length} commands`)
if (registeredCount > 0) {
logger.info('\nCommands have been registered in Salamander\'s User Menu.')
logger.info('Restart Salamander to see the new menu entries.')
}
} catch (error) {
logger.error('Failed to register commands:', error)
process.exit(1)
}
}
cli.command(command, desc, builder, handler)

View File

@ -0,0 +1,501 @@
import * as fs from 'fs';
import * as path from 'path';
import regedit from 'regedit';
const promisified = regedit.promisified;
export interface SalamanderMenuItem {
name: string;
command?: string;
arguments?: string;
initialDirectory?: string;
executeThoughShell?: boolean;
closeShellWindow?: boolean;
openShellWindow?: boolean;
icon?: string;
showInToolbar?: boolean;
type?: 'command' | 'submenu' | 'submenu-end';
children?: SalamanderMenuItem[];
}
export interface SalamanderMenuConfig {
baseKey: string;
startIndex: number;
items: SalamanderMenuItem[];
}
export interface RegistryEntry {
key: string;
values: Record<string, string | number>;
}
export class SalamanderMenuGenerator {
private baseKey: string = 'HKEY_CURRENT_USER\\Software\\Altap\\Altap Salamander 4.0\\User Menu';
constructor(private config: SalamanderMenuConfig) {
if (config.baseKey) {
this.baseKey = config.baseKey;
}
}
/**
* Parse existing registry file to find the highest menu index
*/
static parseExistingRegistry(registryPath: string): number {
if (!fs.existsSync(registryPath)) {
return 0;
}
const content = fs.readFileSync(registryPath, 'utf8');
const lines = content.split('\n');
let maxIndex = 0;
for (const line of lines) {
const match = line.match(/\\User Menu\\(\d+)\]/);
if (match) {
const index = parseInt(match[1], 10);
if (index > maxIndex) {
maxIndex = index;
}
}
}
return maxIndex;
}
/**
* Generate registry entries from menu configuration
*/
generateRegistryEntries(): RegistryEntry[] {
const entries: RegistryEntry[] = [];
let currentIndex = this.config.startIndex;
const processItems = (items: SalamanderMenuItem[], isSubMenu = false) => {
for (const item of items) {
if (item.children && item.children.length > 0) {
// Submenu start
entries.push({
key: `[${this.baseKey}\\${currentIndex}]`,
values: this.itemToRegistryValues(item, 'submenu')
});
currentIndex++;
// Process children
processItems(item.children, true);
// Submenu end
entries.push({
key: `[${this.baseKey}\\${currentIndex}]`,
values: {
'"Item Name"': '"(Submenu End)"',
'"Command"': '""',
'"Arguments"': '""',
'"Initial Directory"': '""',
'"Execute Through Shell"': 'dword:00000000',
'"Close Shell Window"': 'dword:00000000',
'"Open Shell Window"': 'dword:00000000',
'"Icon"': '""',
'"Type"': 'dword:00000002',
'"Show In Toolbar"': 'dword:00000000'
}
});
currentIndex++;
} else {
// Regular command
entries.push({
key: `[${this.baseKey}\\${currentIndex}]`,
values: this.itemToRegistryValues(item, 'command')
});
currentIndex++;
}
}
};
processItems(this.config.items);
return entries;
}
/**
* Convert menu item to registry values
*/
private itemToRegistryValues(item: SalamanderMenuItem, type: 'command' | 'submenu'): Record<string, string | number> {
const values: Record<string, string | number> = {
'"Item Name"': `"${item.name}"`,
'"Command"': `"${item.command || ''}"`,
'"Arguments"': `"${this.escapeArguments(item.arguments || '')}"`,
'"Initial Directory"': `"${item.initialDirectory || ''}"`,
'"Execute Through Shell"': item.executeThoughShell !== false ? 'dword:00000001' : 'dword:00000000',
'"Close Shell Window"': item.closeShellWindow === true ? 'dword:00000001' : 'dword:00000000',
'"Open Shell Window"': item.openShellWindow !== false ? 'dword:00000001' : 'dword:00000000',
'"Icon"': `"${item.icon || ''}"`,
'"Type"': type === 'submenu' ? 'dword:00000001' : 'dword:00000000',
'"Show In Toolbar"': item.showInToolbar !== false ? 'dword:00000001' : 'dword:00000000'
};
return values;
}
/**
* Escape arguments for registry format
*/
private escapeArguments(args: string): string {
return args
.replace(/\\/g, '\\\\') // Escape backslashes
.replace(/"/g, '\\\\"'); // Escape quotes
}
/**
* Generate registry file content
*/
generateRegistryFile(): string {
const entries = this.generateRegistryEntries();
let content = 'REGEDIT4\n\n';
for (const entry of entries) {
content += `${entry.key}\n`;
for (const [key, value] of Object.entries(entry.values)) {
content += `${key}=${value}\n`;
}
content += '\n';
}
return content;
}
/**
* Save registry file to disk
*/
saveRegistryFile(outputPath: string): void {
const content = this.generateRegistryFile();
// Ensure output directory exists
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, content, 'utf8');
}
/**
* Load menu configuration from JSON file
*/
static loadFromJson(jsonPath: string): SalamanderMenuConfig {
if (!fs.existsSync(jsonPath)) {
throw new Error(`JSON configuration file not found: ${jsonPath}`);
}
const content = fs.readFileSync(jsonPath, 'utf8');
return JSON.parse(content) as SalamanderMenuConfig;
}
/**
* Find insertion point in existing registry
*/
static findInsertionPoint(registryPath: string, groupName?: string): number {
if (!fs.existsSync(registryPath)) {
return 1; // Start at index 1 if no existing registry
}
const maxIndex = this.parseExistingRegistry(registryPath);
if (!groupName) {
return maxIndex + 1;
}
// Find group and insert before submenu end
const content = fs.readFileSync(registryPath, 'utf8');
const lines = content.split('\n');
let inTargetGroup = false;
let groupStartIndex = -1;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Check for menu entry
const menuMatch = line.match(/\\User Menu\\(\d+)\]/);
if (menuMatch) {
const index = parseInt(menuMatch[1], 10);
// Look for item name in next few lines
for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
const nameLine = lines[j];
if (nameLine.includes('"Item Name"')) {
const nameMatch = nameLine.match(/"Item Name"="([^"]+)"/);
if (nameMatch) {
const itemName = nameMatch[1];
if (itemName === groupName) {
inTargetGroup = true;
groupStartIndex = index;
} else if (itemName === '(Submenu End)' && inTargetGroup) {
return index; // Insert before submenu end
}
}
break;
}
}
}
}
return maxIndex + 1; // Fallback to end
}
}
/**
* Windows Registry helper for direct registry operations using regedit package
*/
export class WindowsRegistry {
private static isWindows(): boolean {
return process.platform === 'win32' && promisified !== null;
}
private static ensureRegedit(): void {
if (!this.isWindows()) {
throw new Error('Registry operations are only supported on Windows with regedit package installed');
}
}
/**
* Read all User Menu entries from registry
*/
static async readUserMenuEntries(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Record<string, any>> {
this.ensureRegedit();
try {
const result = await promisified.list([baseKey]);
return result[baseKey] || { exists: false, keys: [], values: {} };
} catch (error) {
return { exists: false, keys: [], values: {} };
}
}
/**
* Read specific menu entry by index
*/
static async readMenuEntry(index: number, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Record<string, any>> {
this.ensureRegedit();
const keyPath = `${baseKey}\\${index}`;
try {
const result = await promisified.list([keyPath]);
return result[keyPath] || { exists: false, keys: [], values: {} };
} catch (error) {
return { exists: false, keys: [], values: {} };
}
}
/**
* Get all existing menu indices
*/
static async getExistingMenuIndices(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<number[]> {
this.ensureRegedit();
const menuData = await this.readUserMenuEntries(baseKey);
if (!menuData.exists) {
return [];
}
return menuData.keys
.map((key: string) => parseInt(key, 10))
.filter((index: number) => !isNaN(index))
.sort((a: number, b: number) => a - b);
}
/**
* Get next available menu index
*/
static async getNextMenuIndex(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<number> {
const indices = await this.getExistingMenuIndices(baseKey);
return indices.length > 0 ? Math.max(...indices) + 1 : 1;
}
/**
* Find insertion point for a specific group
*/
static async findGroupInsertionPoint(groupName: string, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<number> {
this.ensureRegedit();
const indices = await this.getExistingMenuIndices(baseKey);
let inTargetGroup = false;
for (const index of indices) {
const entry = await this.readMenuEntry(index, baseKey);
if (entry.exists && entry.values['Item Name']) {
const itemName = entry.values['Item Name'].value;
if (itemName === groupName) {
inTargetGroup = true;
} else if (itemName === '(Submenu End)' && inTargetGroup) {
return index; // Insert before submenu end
}
}
}
return await this.getNextMenuIndex(baseKey); // Fallback to end
}
/**
* Write registry values for a menu entry
*/
static async writeMenuEntry(index: number, values: Record<string, any>, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<void> {
this.ensureRegedit();
const keyPath = `${baseKey}\\${index}`;
// First create the key
await promisified.createKey([keyPath]);
// Then write the values
const valuesToWrite: Record<string, any> = {};
valuesToWrite[keyPath] = {};
for (const [valueName, valueData] of Object.entries(values)) {
const cleanValueName = valueName.replace(/"/g, '');
let cleanValueData = String(valueData);
let valueType = 'REG_SZ';
if (cleanValueData.startsWith('dword:')) {
valueType = 'REG_DWORD';
cleanValueData = parseInt(cleanValueData.replace('dword:', ''), 16).toString();
} else {
// Only strip quotes for non-Arguments fields to preserve command-line quoting
if (cleanValueName !== 'Arguments') {
cleanValueData = cleanValueData.replace(/"/g, '');
}
}
valuesToWrite[keyPath][cleanValueName] = {
value: cleanValueData,
type: valueType
};
}
await promisified.putValue(valuesToWrite);
}
/**
* Delete a menu entry
*/
static async deleteMenuEntry(index: number, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<void> {
this.ensureRegedit();
const keyPath = `${baseKey}\\${index}`;
try {
await promisified.deleteKey([keyPath]);
} catch (error) {
// Key might not exist, ignore error
}
}
/**
* List all menu entries with their details
*/
static async listAllMenuEntries(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Array<{index: number, name: string, type: string, command?: string, arguments?: string}>> {
const indices = await this.getExistingMenuIndices(baseKey);
const entries = [];
for (const index of indices) {
const entry = await this.readMenuEntry(index, baseKey);
if (entry.exists && entry.values['Item Name']) {
const name = entry.values['Item Name'].value;
const type = entry.values['Type'] ? entry.values['Type'].value : 0;
const command = entry.values['Command'] ? entry.values['Command'].value : '';
const args = entry.values['Arguments'] ? entry.values['Arguments'].value : '';
let typeString = 'command';
if (type === 1 || type === '0x00000001') typeString = 'submenu';
if (type === 2 || type === '0x00000002') typeString = 'submenu-end';
entries.push({
index,
name,
type: typeString,
command: command || undefined,
arguments: args || undefined
});
}
}
return entries;
}
}
/**
* Extended Salamander Menu Generator with direct registry support
*/
export class SalamanderMenuGeneratorRegistry extends SalamanderMenuGenerator {
/**
* Apply menu configuration directly to Windows registry
*/
async applyToRegistry(): Promise<void> {
if (process.platform !== 'win32') {
throw new Error('Direct registry operations are only supported on Windows');
}
const entries = this.generateRegistryEntries();
for (const entry of entries) {
// Extract menu index from key
const keyMatch = entry.key.match(/\\User Menu\\(\d+)\]/);
if (!keyMatch) continue;
const menuIndex = parseInt(keyMatch[1], 10);
await WindowsRegistry.writeMenuEntry(menuIndex, entry.values);
}
}
/**
* Remove menu entries from Windows registry
*/
async removeFromRegistry(): Promise<void> {
if (process.platform !== 'win32') {
throw new Error('Direct registry operations are only supported on Windows');
}
const entries = this.generateRegistryEntries();
// Remove in reverse order to avoid index issues
for (let i = entries.length - 1; i >= 0; i--) {
const entry = entries[i];
const keyMatch = entry.key.match(/\\User Menu\\(\d+)\]/);
if (keyMatch) {
const menuIndex = parseInt(keyMatch[1], 10);
await WindowsRegistry.deleteMenuEntry(menuIndex);
}
}
}
/**
* Auto-detect insertion point from Windows registry
*/
static async autoDetectInsertionPoint(groupName?: string): Promise<number> {
if (process.platform !== 'win32') {
throw new Error('Registry operations are only supported on Windows');
}
if (groupName) {
return await WindowsRegistry.findGroupInsertionPoint(groupName);
} else {
return await WindowsRegistry.getNextMenuIndex();
}
}
/**
* List current menu entries from registry
*/
static async listCurrentMenuEntries(): Promise<Array<{index: number, name: string, type: string, command?: string, arguments?: string}>> {
return await WindowsRegistry.listAllMenuEntries();
}
}
export default SalamanderMenuGenerator;

View File

@ -0,0 +1,15 @@
#!/usr/bin/env node
import { defaults } from './_cli.js'; defaults()
import { cli } from './cli.js'
import './commands/register-commands.js'
const argv: any = cli.argv;
if (argv.h || argv.help) {
cli.showHelp();
process.exit();
} else if (argv.v || argv.version) {
process.exit();
}

View File

@ -4,7 +4,8 @@
"src/**/*.ts"
],
"files": [
"src/index.ts"
"src/index.ts",
"src/main.ts"
],
"compilerOptions": {
"strictNullChecks": false,

View File

@ -31,6 +31,8 @@ export const sanitize = (argv) => {
withoutReduction: argv.withoutReduction,
fastShrinkOnLoad: argv.fastShrinkOnLoad,
background: argv.background,
square: argv.square,
fillColor: argv.fillColor,
...argv
};
let srcInfo;
@ -78,4 +80,4 @@ export const sanitize = (argv) => {
options.variables = variables;
return options;
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2NsaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9fY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxhQUFhLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBQyxVQUFVLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNsRixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUM3RCxPQUFPLEVBQUUsSUFBSSxJQUFJLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFBO0FBQ3BELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUV4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxFQUFFO0lBQ3pCLE1BQU0sY0FBYyxHQUFHLE1BQU0sQ0FBQztJQUM5QixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzVCLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBQ3RDLENBQUM7SUFDRCxPQUFPLENBQUMsRUFBRSxDQUFDLG9CQUFvQixFQUFFLENBQUMsTUFBYyxFQUFFLEVBQUU7UUFDaEQsT0FBTyxDQUFDLEtBQUssQ0FBQywrQkFBK0IsRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUMxRCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQTtBQUVELE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxDQUFDLElBQVMsRUFBbUIsRUFBRTtJQUVuRCxNQUFNLE9BQU8sR0FBbUI7UUFDNUIsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1FBQ2pCLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLE9BQU8s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
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2NsaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9fY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxhQUFhLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBQyxVQUFVLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNsRixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUM3RCxPQUFPLEVBQUUsSUFBSSxJQUFJLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFBO0FBQ3BELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUV4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxFQUFFO0lBQ3pCLE1BQU0sY0FBYyxHQUFHLE1BQU0sQ0FBQztJQUM5QixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzVCLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBQ3RDLENBQUM7SUFDRCxPQUFPLENBQUMsRUFBRSxDQUFDLG9CQUFvQixFQUFFLENBQUMsTUFBYyxFQUFFLEVBQUU7UUFDaEQsT0FBTyxDQUFDLEtBQUssQ0FBQywrQkFBK0IsRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUMxRCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQTtBQUVELE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxDQUFDLElBQVMsRUFBbUIsRUFBRTtJQUVuRCxNQUFNLE9BQU8sR0FBbUI7UUFDNUIsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1FBQ2pCLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLE9BQU8s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

File diff suppressed because one or more lines are too long

View File

@ -46,6 +46,14 @@ export const defaultOptions = (yargs) => {
}).option('percent', {
describe: 'Resize image in percent (width)',
type: 'number'
}).option('square', {
default: false,
describe: 'Fit image within width for 1:1 aspect ratio without cropping',
type: 'boolean'
}).option('fillColor', {
describe: 'Fill color for square backgrounds (default: white)',
type: 'string',
default: 'white'
}).option('logLevel', {
describe: 'Log level : warn, info, debug, error',
type: 'string',
@ -63,4 +71,4 @@ export async function handler(argv) {
await resize(options);
}
cli.command(command, desc, builder, handler);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzaXplLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL3Jlc2l6ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFBO0FBQ3BDLE9BQU8sRUFDSCxNQUFNLEVBQ1QsTUFBTSwrQkFBK0IsQ0FBQTtBQUN0QyxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBQy9CLE9BQU8sRUFDSCxRQUFRLEVBQ1IsUUFBUSxFQUNYLE1BQU0sWUFBWSxDQUFBO0FBTW5CLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQzlDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDdkIsUUFBUSxFQUFFLGtCQUFrQjtRQUM1QixZQUFZLEVBQUUsSUFBSTtLQUNyQixDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRTtRQUNiLFFBQVEsRUFBRSxrQkFBa0I7S0FDL0IsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDZixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSxnQ0FBZ0M7UUFDMUMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx3QkFBd0I7UUFDbEMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsT0FBTyxFQUFFLEtBQUs7UUFDZCxRQUFRLEVBQUUsd0JBQXdCO1FBQ2xDLElBQUksRUFBRSxTQUFTO0tBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLE9BQU8sRUFBRSxLQUFLO1FBQ2QsUUFBUSxFQUFFLDJCQUEyQjtRQUNyQyxJQUFJLEVBQUUsUUFBUTtLQUNqQixDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRTtRQUNmLFFBQVEsRUFBRSxtQkFBbUI7UUFDN0IsSUFBSSxFQUFFLFFBQVE7S0FDakIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUU7UUFDaEIsUUFBUSxFQUFFLHFCQUFxQjtRQUMvQixJQUFJLEVBQUUsUUFBUTtLQUNqQixDQUFDLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRTtRQUNuQixRQUFRLEVBQUUsNkJBQTZCO1FBQ3ZDLElBQUksRUFBRSxRQUFRO0tBQ2pCLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFO1FBQ2xCLFFBQVEsRUFBRSw0QkFBNEI7UUFDdEMsSUFBSSxFQUFFLFFBQVE7S0FDakIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsUUFBUSxFQUFFLDJCQUEyQjtRQUNyQyxJQUFJLEVBQUUsUUFBUTtLQUNqQixDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRTtRQUNqQixRQUFRLEVBQUUsaUNBQWlDO1FBQzNDLElBQUksRUFBRSxRQUFRO0tBQ2pCLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFO1FBQ2xCLFFBQVEsRUFBRSxzQ0FBc0M7UUFDaEQsSUFBSSxFQUFFLFFBQVE7UUFDZCxPQUFPLEVBQUUsTUFBTTtLQUNsQixDQUFDLENBQUE7QUFDTixDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDO0FBQ2hDLE1BQU0sQ0FBQyxNQUFNLElBQUksR0FBRyxlQUFlLENBQUM7QUFDcEMsTUFBTSxDQUFDLE1BQU0sT0FBTyxHQUFHLGNBQWMsQ0FBQztBQUV0QyxNQUFNLENBQUMsS0FBSyxVQUFVLE9BQU8sQ0FBQyxJQUFtQjtJQUM3QyxRQUFRLEVBQUUsQ0FBQTtJQUNWLE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQWEsQ0FBQTtJQUMxQyxNQUFNLENBQUMsUUFBUSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBZSxDQUFBO0lBQ2xELE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUE7SUFDM0MsTUFBTSxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUE7QUFDekIsQ0FBQztBQUVELEdBQUcsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUEifQ==
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzaXplLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL3Jlc2l6ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFBO0FBQ3BDLE9BQU8sRUFDSCxNQUFNLEVBQ1QsTUFBTSwrQkFBK0IsQ0FBQTtBQUN0QyxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBQy9CLE9BQU8sRUFDSCxRQUFRLEVBQ1IsUUFBUSxFQUNYLE1BQU0sWUFBWSxDQUFBO0FBTW5CLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQzlDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDdkIsUUFBUSxFQUFFLGtCQUFrQjtRQUM1QixZQUFZLEVBQUUsSUFBSTtLQUNyQixDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRTtRQUNiLFFBQVEsRUFBRSxrQkFBa0I7S0FDL0IsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDZixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSxnQ0FBZ0M7UUFDMUMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx3QkFBd0I7UUFDbEMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsT0FBTyxFQUFFLEtBQUs7UUFDZCxRQUFRLEVBQUUsd0JBQXdCO1FBQ2xDLElBQUksRUFBRSxTQUFTO0tBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLE9BQU8sRUFBRSxLQUFLO1FBQ2QsUUFBUSxFQUFFLDJCQUEyQjtRQUNyQyxJQUFJLEVBQUUsUUFBUTtLQUNqQixDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRTtRQUNmLFFBQVEsRUFBRSxtQkFBbUI7UUFDN0IsSUFBSSxFQUFFLFFBQVE7S0FDakIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUU7UUFDaEIsUUFBUSxFQUFFLHFCQUFxQjtRQUMvQixJQUFJLEVBQUUsUUFBUTtLQUNqQixDQUFDLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRTtRQUNuQixRQUFRLEVBQUUsNkJBQTZCO1FBQ3ZDLElBQUksRUFBRSxRQUFRO0tBQ2pCLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFO1FBQ2xCLFFBQVEsRUFBRSw0QkFBNEI7UUFDdEMsSUFBSSxFQUFFLFFBQVE7S0FDakIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsUUFBUSxFQUFFLDJCQUEyQjtRQUNyQyxJQUFJLEVBQUUsUUFBUTtLQUNqQixDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRTtRQUNqQixRQUFRLEVBQUUsaUNBQWlDO1FBQzNDLElBQUksRUFBRSxRQUFRO0tBQ2pCLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFO1FBQ2hCLE9BQU8sRUFBRSxLQUFLO1FBQ2QsUUFBUSxFQUFFLDhEQUE4RDtRQUN4RSxJQUFJLEVBQUUsU0FBUztLQUNsQixDQUFDLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRTtRQUNuQixRQUFRLEVBQUUsb0RBQW9EO1FBQzlELElBQUksRUFBRSxRQUFRO1FBQ2QsT0FBTyxFQUFFLE9BQU87S0FDbkIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUU7UUFDbEIsUUFBUSxFQUFFLHNDQUFzQztRQUNoRCxJQUFJLEVBQUUsUUFBUTtRQUNkLE9BQU8sRUFBRSxNQUFNO0tBQ2xCLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQTtBQUVELE1BQU0sQ0FBQyxNQUFNLE9BQU8sR0FBRyxRQUFRLENBQUM7QUFDaEMsTUFBTSxDQUFDLE1BQU0sSUFBSSxHQUFHLGVBQWUsQ0FBQztBQUNwQyxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsY0FBYyxDQUFDO0FBRXRDLE1BQU0sQ0FBQyxLQUFLLFVBQVUsT0FBTyxDQUFDLElBQW1CO0lBQzdDLFFBQVEsRUFBRSxDQUFBO0lBQ1YsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBYSxDQUFBO0lBQzFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFlLENBQUE7SUFDbEQsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQTtJQUMzQyxNQUFNLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQTtBQUN6QixDQUFDO0FBRUQsR0FBRyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQSJ9

File diff suppressed because one or more lines are too long

View File

@ -99,6 +99,7 @@ export declare class WindowsRegistry {
name: string;
type: string;
command?: string;
arguments?: string;
}>>;
}
/**
@ -125,6 +126,7 @@ export declare class SalamanderMenuGeneratorRegistry extends SalamanderMenuGener
name: string;
type: string;
command?: string;
arguments?: string;
}>>;
}
export default SalamanderMenuGenerator;

File diff suppressed because one or more lines are too long

View File

@ -44,6 +44,8 @@ export type IResizeOptions = IOptions & IResizeOptionsSharp & {
minWidth?: number;
minHeight?: number;
minSize?: number;
square?: boolean;
fillColor?: string;
};
export type IConvertVideoOptions = IOptions & {
interval?: number;

View File

@ -1 +0,0 @@
mock-png-data

File diff suppressed because it is too large Load Diff

View File

@ -40,9 +40,11 @@
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/showdown": "^2.0.6",
"nexe": "^5.0.0-beta.4",
"vitest": "^3.1.1"
},
"scripts": {
"register": "pm-media register-commands --group 'Media'",
"test": "tsc; mocha --full-trace mocha \"spec/**/*.spec.js\"",
"test:pdf": "vitest run tests/pdf",
"test-with-coverage": "istanbul cover node_modules/.bin/_mocha -- 'spec/**/*.spec.js'",

View File

@ -0,0 +1,56 @@
{
"resize": {
"name": "Resize Images",
"command": "pm-media",
"args": "resize --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_resized.&{SRC_EXT}\"",
"description": "Resize images"
},
"watermark": {
"name": "Add Watermark",
"command": "pm-media",
"args": "watermark --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --watermark=\"&{POLYMECH-ROOT}/nordin-ex/branding/logos/polymech-saw-ex.svg\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_watermarked.&{SRC_EXT}\"",
"description": "Add watermark to images"
},
"background-remove": {
"name": "Remove Background",
"command": "pm-media",
"args": "background:remove --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_bg_removed.&{SRC_EXT}\"",
"description": "Remove background from images"
},
"background-remove-bria": {
"name": "Remove Background (Bria AI)",
"command": "pm-media",
"args": "background:remove:bria --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_bria_bg_removed.&{SRC_EXT}\"",
"description": "Remove background using Bria AI"
},
"convert": {
"name": "Convert Format",
"command": "pm-media",
"args": "convert --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_converted.jpg\"",
"description": "Convert image format"
},
"pdf2jpg": {
"name": "PDF to JPG",
"command": "pm-media",
"args": "pdf2jpg --alt=true --logLevel=info --input=\"$(FullName)\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_page.jpg\"",
"description": "Convert PDF pages to JPG images"
},
"svg2jpg": {
"name": "SVG to JPG",
"command": "pm-media",
"args": "svg2jpg --alt=true --logLevel=info --src=\"$(FullName)/**/*.svg\" --dst=\"&{SRC_DIR}/&{SRC_NAME}.jpg\"",
"description": "Convert SVG to JPG"
},
"crop-foreground": {
"name": "Crop Foreground",
"command": "pm-media",
"args": "crop-foreground --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_cropped.&{SRC_EXT}\"",
"description": "Crop to foreground content"
},
"resize-square": {
"name": "Resize to Square",
"command": "pm-media",
"args": "resize --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_sq.&{SRC_EXT}\" --width=1980 --square --fillColor=white",
"description": "Resize images to square format (1980x1980) with white background"
}
}

View File

@ -1,14 +0,0 @@
REGEDIT4
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\1]
"Item Name"="Watermark"
"Command"="pm-media"
"Arguments"="watermark --src=\\" \\ --registry --group Media"
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001

View File

@ -0,0 +1,95 @@
// nexe.js - Compile pm-media to Windows executable using nexe Node.js API
import { compile } from 'nexe';
import path from 'path';
import fs from 'fs';
async function buildExecutable() {
console.log('🔨 Building pm-media Windows executable...');
const outputDir = './dist/win-64';
const outputFile = 'pm-media.exe';
const entryPoint = './dist-in/main.js';
const nexeTemp = '../nexe';
const nodeVersion = '20.11.1';
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
console.log(`📁 Created output directory: ${outputDir}`);
}
// Ensure nexe temp directory exists
if (!fs.existsSync(nexeTemp)) {
fs.mkdirSync(nexeTemp, { recursive: true });
console.log(`📁 Created temp directory: ${nexeTemp}`);
}
// Check if entry point exists
if (!fs.existsSync(entryPoint)) {
console.log(`❌ Entry point ${entryPoint} not found. Please run 'npm run build' first.`);
process.exit(1);
}
const outputPath = path.join(outputDir, outputFile);
console.log('📦 Compiling with nexe...');
console.log(` Entry: ${entryPoint}`);
console.log(` Output: ${outputPath}`);
console.log(` Temp: ${nexeTemp}`);
console.log(` Target: windows-x64-${nodeVersion}`);
try {
await compile({
input: entryPoint,
output: outputPath,
target: `windows-x64-${nodeVersion}`,
build: true, // Build from source for native modules like sharp
temp: nexeTemp,
name: 'pm-media',
configure: ['--with-intl=full-icu'], // Full ICU support
make: ['-j4'], // Parallel build
loglevel: 'verbose',
// Resources - include any additional files if needed
resources: [
// Add any resource patterns here if needed
// './assets/**/*'
],
patches: [
// Patch for better native module support
async (compiler, next) => {
// This patch helps with native modules like sharp
await compiler.replaceInFileAsync(
'lib/internal/bootstrap/pre_execution.js',
'process.dlopen = function(',
`
// Nexe patch for native modules
const originalDlopen = process.dlopen;
process.dlopen = function(`
);
return next();
}
]
});
console.log(`✅ Successfully compiled to ${outputPath}`);
// Show file size
if (fs.existsSync(outputPath)) {
const stats = fs.statSync(outputPath);
const fileSizeInMB = (stats.size / (1024 * 1024)).toFixed(2);
console.log(`📊 Executable size: ${fileSizeInMB} MB`);
}
console.log('🎉 Build complete!');
} catch (error) {
console.error('❌ Compilation failed:', error.message);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
// Run the build
buildExecutable().catch(console.error);

View File

@ -35,6 +35,8 @@ export const sanitize = (argv: any): IResizeOptions => {
withoutReduction: argv.withoutReduction,
fastShrinkOnLoad: argv.fastShrinkOnLoad,
background: argv.background,
square: argv.square,
fillColor: argv.fillColor,
...argv
} as IResizeOptions

View File

@ -4,13 +4,17 @@ import * as path from 'path'
import { logger } from '../index.js'
import { cli } from '../cli.js'
import { defaults } from '../_cli.js'
import { SalamanderMenuGeneratorRegistry, WindowsRegistry } from '../lib/salamander/index.js'
import { WindowsRegistry } from '../lib/salamander/index.js'
export const defaultOptions = (yargs: CLI.Argv) => {
return yargs.option('group', {
describe: 'Group name to register commands under',
type: 'string',
default: 'Media'
}).option('commands', {
describe: 'Path to JSON file with command mappings',
type: 'string',
default: './salamand.json'
}).option('dry', {
default: false,
describe: 'Show what would be registered without actually registering',
@ -37,8 +41,8 @@ interface CommandInfo {
description: string
}
// Basic command mappings - users can extend these as needed
const COMMAND_MAPPINGS: Record<string, CommandInfo> = {
// Default command mappings - can be overridden by JSON file
const DEFAULT_COMMAND_MAPPINGS: Record<string, CommandInfo> = {
'resize': {
name: 'Resize Images',
command: 'pm-media',
@ -48,7 +52,7 @@ const COMMAND_MAPPINGS: Record<string, CommandInfo> = {
'watermark': {
name: 'Add Watermark',
command: 'pm-media',
args: 'watermark --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --watermark=\"&{POLYMECH-ROOT}/nordin-ex/branding/polymech-saw-ex.svg\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_watermarked.&{SRC_EXT}\"',
args: 'watermark --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --watermark=\"&{POLYMECH-ROOT}/nordin-ex/branding/logos/polymech-saw-ex.svg\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_watermarked.&{SRC_EXT}\"',
description: 'Add watermark to images'
},
'background-remove': {
@ -72,7 +76,7 @@ const COMMAND_MAPPINGS: Record<string, CommandInfo> = {
'pdf2jpg': {
name: 'PDF to JPG',
command: 'pm-media',
args: 'pdf2jpg --alt=true --logLevel=info --src=\"$(FullName)/**/*.pdf\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_page.jpg\"',
args: 'pdf2jpg --alt=true --logLevel=info --input=\"$(FullName)\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_page.jpg\"',
description: 'Convert PDF pages to JPG images'
},
'svg2jpg': {
@ -86,19 +90,55 @@ const COMMAND_MAPPINGS: Record<string, CommandInfo> = {
command: 'pm-media',
args: 'crop-foreground --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_cropped.&{SRC_EXT}\"',
description: 'Crop to foreground content'
},
'resize-square': {
name: 'Resize to Square',
command: 'pm-media',
args: 'resize --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_sq.&{SRC_EXT}\" --width=1980 --square --fillColor=white',
description: 'Resize images to square format (1980x1980) with white background'
}
}
async function getAvailableCommands(): Promise<string[]> {
/**
* Load command mappings from JSON file or use defaults
*/
function loadCommandMappings(configPath: string): Record<string, CommandInfo> {
try {
if (fs.existsSync(configPath)) {
const configContent = fs.readFileSync(configPath, 'utf8')
const config = JSON.parse(configContent)
logger.info(`Loaded command mappings from: ${configPath}`)
return config
} else {
logger.info(`Config file not found (${configPath}), using default mappings`)
return DEFAULT_COMMAND_MAPPINGS
}
} catch (error) {
logger.warn(`Failed to load config file (${configPath}):`, error)
logger.info('Using default command mappings')
return DEFAULT_COMMAND_MAPPINGS
}
}
async function getAvailableCommands(commandMappings: Record<string, CommandInfo>): Promise<string[]> {
const commandsDir = path.join(process.cwd(), 'src', 'commands')
const files = fs.readdirSync(commandsDir)
return files
// Get commands from actual files
const fileBasedCommands = files
.filter(file => file.endsWith('.ts') &&
file !== 'salamander.ts' &&
file !== 'register-commands.ts')
.map(file => file.replace('.ts', ''))
.filter(cmd => COMMAND_MAPPINGS[cmd]) // Only include commands we have mappings for
.filter(cmd => commandMappings[cmd])
// Get all commands from mappings (includes custom commands like resize-square)
const allMappingCommands = Object.keys(commandMappings)
// Combine and deduplicate
const allCommands = [...new Set([...fileBasedCommands, ...allMappingCommands])]
return allCommands
}
export async function handler(argv: CLI.Arguments) {
@ -107,19 +147,23 @@ export async function handler(argv: CLI.Arguments) {
const options = {
group: argv.group as string,
commands: argv.commands as string,
dry: argv.dry as boolean,
force: argv.force as boolean
}
try {
// Load command mappings from file or use defaults
const commandMappings = loadCommandMappings(options.commands)
logger.info('Scanning available pm-media commands...')
const availableCommands = await getAvailableCommands()
const availableCommands = await getAvailableCommands(commandMappings)
logger.info(`Found ${availableCommands.length} commands: ${availableCommands.join(', ')}`)
if (options.dry) {
logger.info('\n=== DRY RUN - Commands that would be registered ===')
for (const cmdName of availableCommands) {
const cmdInfo = COMMAND_MAPPINGS[cmdName]
const cmdInfo = commandMappings[cmdName]
logger.info(`\nCommand: ${cmdName}`)
logger.info(` Name: ${cmdInfo.name}`)
logger.info(` Args: ${cmdInfo.args}`)
@ -131,11 +175,10 @@ export async function handler(argv: CLI.Arguments) {
// Check which commands already exist
logger.info('Checking existing registry entries...')
const existingEntries = await WindowsRegistry.listAllMenuEntries()
const existingCommandNames = existingEntries
const existingCommands = existingEntries
.filter(entry => entry.type === 'command') // Only commands, not submenus
.map(entry => entry.name)
logger.info(`Found ${existingCommandNames.length} existing menu entries`)
logger.info(`Found ${existingCommands.length} existing command entries`)
let registeredCount = 0
let skippedCount = 0
@ -178,13 +221,17 @@ export async function handler(argv: CLI.Arguments) {
}
for (const cmdName of availableCommands) {
const cmdInfo = COMMAND_MAPPINGS[cmdName]
const cmdInfo = commandMappings[cmdName]
// Check if command already exists
const exists = existingCommandNames.some(name =>
name.toLowerCase().includes(cmdInfo.name.toLowerCase()) ||
cmdInfo.name.toLowerCase().includes(name.toLowerCase())
)
// Check if command already exists - look for exact name AND command match
const exists = existingCommands.some(entry => {
const nameMatch = entry.name === cmdInfo.name
const commandMatch = entry.command === cmdInfo.command
const argsMatch = entry.arguments === cmdInfo.args
// Consider it a duplicate if name and command match (even if args differ slightly)
return nameMatch && commandMatch
})
if (exists && !options.force) {
logger.info(`Skipping '${cmdInfo.name}' - already exists (use --force to override)`)

View File

@ -57,6 +57,14 @@ export const defaultOptions = (yargs: CLI.Argv) => {
}).option('percent', {
describe: 'Resize image in percent (width)',
type: 'number'
}).option('square', {
default: false,
describe: 'Fit image within width for 1:1 aspect ratio without cropping',
type: 'boolean'
}).option('fillColor', {
describe: 'Fill color for square backgrounds (default: white)',
type: 'string',
default: 'white'
}).option('logLevel', {
describe: 'Log level : warn, info, debug, error',
type: 'string',

View File

@ -89,10 +89,39 @@ export const resizeFile = async (source: string, target: string, onNode: (data:
})
} else if (options.width || options.height) {
image = image.resize({
width: options.width,
...resizeOptions
})
if (options.square && options.width) {
// For square mode, fit image within width maintaining aspect ratio
// then center on square canvas with fill color
const squareSize = options.width
const fillColor = options.fillColor || 'white'
// First resize to fit within the square while maintaining aspect ratio
image = image.resize({
width: squareSize,
height: squareSize,
fit: 'inside', // Fit within bounds without cropping
background: { r: 255, g: 255, b: 255, alpha: 0 } // Transparent background initially
})
// Then extend to exact square size with fill color background
image = image.extend({
top: 0,
bottom: 0,
left: 0,
right: 0,
background: fillColor
}).resize({
width: squareSize,
height: squareSize,
fit: 'contain', // Center the image
background: fillColor
})
} else {
image = image.resize({
width: options.width,
...resizeOptions
})
}
} else {
logger.error(`Error resizing, invalid options for ${source} - no width, height or percent`)
return image

View File

@ -364,7 +364,10 @@ export class WindowsRegistry {
valueType = 'REG_DWORD';
cleanValueData = parseInt(cleanValueData.replace('dword:', ''), 16).toString();
} else {
cleanValueData = cleanValueData.replace(/"/g, '');
// Only strip quotes for non-Arguments fields to preserve command-line quoting
if (cleanValueName !== 'Arguments') {
cleanValueData = cleanValueData.replace(/"/g, '');
}
}
valuesToWrite[keyPath][cleanValueName] = {
@ -394,7 +397,7 @@ export class WindowsRegistry {
/**
* List all menu entries with their details
*/
static async listAllMenuEntries(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Array<{index: number, name: string, type: string, command?: string}>> {
static async listAllMenuEntries(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Array<{index: number, name: string, type: string, command?: string, arguments?: string}>> {
const indices = await this.getExistingMenuIndices(baseKey);
const entries = [];
@ -405,6 +408,7 @@ export class WindowsRegistry {
const name = entry.values['Item Name'].value;
const type = entry.values['Type'] ? entry.values['Type'].value : 0;
const command = entry.values['Command'] ? entry.values['Command'].value : '';
const args = entry.values['Arguments'] ? entry.values['Arguments'].value : '';
let typeString = 'command';
if (type === 1 || type === '0x00000001') typeString = 'submenu';
@ -414,7 +418,8 @@ export class WindowsRegistry {
index,
name,
type: typeString,
command: command || undefined
command: command || undefined,
arguments: args || undefined
});
}
}
@ -488,7 +493,7 @@ export class SalamanderMenuGeneratorRegistry extends SalamanderMenuGenerator {
/**
* List current menu entries from registry
*/
static async listCurrentMenuEntries(): Promise<Array<{index: number, name: string, type: string, command?: string}>> {
static async listCurrentMenuEntries(): Promise<Array<{index: number, name: string, type: string, command?: string, arguments?: string}>> {
return await WindowsRegistry.listAllMenuEntries();
}
}

View File

@ -47,6 +47,8 @@ export type IResizeOptions = IOptions & IResizeOptionsSharp & {
minWidth?: number
minHeight?: number
minSize?: number
square?: boolean
fillColor?: string
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB