media | commons - sal
This commit is contained in:
parent
c7f92ece52
commit
9bafb02aa9
1
packages/.gitignore
vendored
1
packages/.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
part-registry
|
||||
nexe
|
||||
2
packages/commons/dist/_cli.d.ts
vendored
Normal file
2
packages/commons/dist/_cli.d.ts
vendored
Normal 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
16
packages/commons/dist/_cli.js
vendored
Normal 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
1
packages/commons/dist/cli.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export declare const cli: any;
|
||||
4
packages/commons/dist/cli.js
vendored
Normal file
4
packages/commons/dist/cli.js
vendored
Normal 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
|
||||
6
packages/commons/dist/commands/register-commands.d.ts
vendored
Normal file
6
packages/commons/dist/commands/register-commands.d.ts
vendored
Normal 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>;
|
||||
208
packages/commons/dist/commands/register-commands.js
vendored
Normal file
208
packages/commons/dist/commands/register-commands.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
packages/commons/dist/commands/salamander.d.ts
vendored
Normal file
6
packages/commons/dist/commands/salamander.d.ts
vendored
Normal 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>;
|
||||
247
packages/commons/dist/commands/salamander.js
vendored
Normal file
247
packages/commons/dist/commands/salamander.js
vendored
Normal file
File diff suppressed because one or more lines are too long
132
packages/commons/dist/lib/salamander/index.d.ts
vendored
Normal file
132
packages/commons/dist/lib/salamander/index.d.ts
vendored
Normal 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;
|
||||
408
packages/commons/dist/lib/salamander/index.js
vendored
Normal file
408
packages/commons/dist/lib/salamander/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
packages/commons/dist/main.d.ts
vendored
Normal file
2
packages/commons/dist/main.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
import './commands/register-commands.js';
|
||||
14
packages/commons/dist/main.js
vendored
Normal file
14
packages/commons/dist/main.js
vendored
Normal 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==
|
||||
@ -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"
|
||||
|
||||
8
packages/commons/salamand.json
Normal file
8
packages/commons/salamand.json
Normal 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"
|
||||
}
|
||||
}
|
||||
16
packages/commons/src/_cli.ts
Normal file
16
packages/commons/src/_cli.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
4
packages/commons/src/cli.ts
Normal file
4
packages/commons/src/cli.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import yargs from 'yargs'
|
||||
import { hideBin } from 'yargs/helpers'
|
||||
|
||||
export const cli = yargs(hideBin(process.argv))
|
||||
252
packages/commons/src/commands/register-commands.ts
Normal file
252
packages/commons/src/commands/register-commands.ts
Normal 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)
|
||||
501
packages/commons/src/lib/salamander/index.ts
Normal file
501
packages/commons/src/lib/salamander/index.ts
Normal 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;
|
||||
15
packages/commons/src/main.ts
Normal file
15
packages/commons/src/main.ts
Normal 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();
|
||||
}
|
||||
@ -4,7 +4,8 @@
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"files": [
|
||||
"src/index.ts"
|
||||
"src/index.ts",
|
||||
"src/main.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": false,
|
||||
|
||||
@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2NsaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9fY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxhQUFhLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBQyxVQUFVLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNsRixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUM3RCxPQUFPLEVBQUUsSUFBSSxJQUFJLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFBO0FBQ3BELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUV4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxFQUFFO0lBQ3pCLE1BQU0sY0FBYyxHQUFHLE1BQU0sQ0FBQztJQUM5QixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzVCLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBQ3RDLENBQUM7SUFDRCxPQUFPLENBQUMsRUFBRSxDQUFDLG9CQUFvQixFQUFFLENBQUMsTUFBYyxFQUFFLEVBQUU7UUFDaEQsT0FBTyxDQUFDLEtBQUssQ0FBQywrQkFBK0IsRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUMxRCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQTtBQUVELE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxDQUFDLElBQVMsRUFBbUIsRUFBRTtJQUVuRCxNQUFNLE9BQU8sR0FBbUI7UUFDNUIsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1FBQ2pCLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTztRQUNyQixLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssS0FBSyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUs7UUFDcEQsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLEtBQUssS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNO1FBQ3ZELFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUTtRQUM3RCxTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVMsS0FBSyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVM7UUFDaEUsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPLEtBQUssS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPO1FBQzFELEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtRQUN2QixRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7UUFDdkIsa0JBQWtCLEVBQUUsSUFBSSxDQUFDLGtCQUFrQjtRQUMzQyxnQkFBZ0IsRUFBRSxJQUFJLENBQUMsZ0JBQWdCO1FBQ3ZDLGdCQUFnQixFQUFFLElBQUksQ0FBQyxnQkFBZ0I7UUFDdkMsVUFBVSxFQUFFLElBQUksQ0FBQyxVQUFVO1FBQzNCLEdBQUcsSUFBSTtLQUNRLENBQUE7SUFFbkIsSUFBSSxPQUFPLENBQUE7SUFFWCxJQUFJLFNBQVMsR0FBRztRQUNaLE1BQU0sRUFBRSxVQUFVO1FBQ2xCLEdBQUcsT0FBTyxDQUFDLFNBQVM7S0FDdkIsQ0FBQTtJQUVELElBQUksT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRWQsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLEdBQUcsRUFBRSxTQUFTLENBQUMsQ0FBQTtRQUMxRCxPQUFPLENBQUMsR0FBRyxHQUFHLGFBQWEsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxLQUFLLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQTtRQUV0RSxpRUFBaUU7UUFDakUsc0VBQXNFO1FBQ3RFLE1BQU0sU0FBUyxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUE7UUFDdkMsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQ3ZFLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQy9CLE9BQU8sQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFBO1FBQ3RCLENBQUM7UUFDRCxPQUFPLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxHQUFHLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQTtRQUNoRSxJQUFJLE9BQU8sSUFBSSxPQUFPLENBQUMsS0FBSyxJQUFJLE9BQU8sQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDbkQsT0FBTyxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUE7WUFDekIsS0FBSyxNQUFNLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQztnQkFDeEIsSUFBSSxNQUFNLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUM7b0JBQ3JELFNBQVMsQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUMzQyxDQUFDO1lBQ0wsQ0FBQztRQUNMLENBQUM7YUFBTSxDQUFDO1lBQ0osT0FBTyxDQUFDLEdBQUcsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxDQUFBO1FBQzlELENBQUM7SUFDTCxDQUFDO0lBQ0QsT0FBTyxDQUFDLE9BQU8sR0FBRyxVQUFVLENBQUMsT0FBTyxDQUFDLEdBQUcsSUFBSSxFQUFFLENBQUMsQ0FBQTtJQUMvQyxJQUFJLE9BQU8sQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ2pDLElBQUksT0FBTyxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDckMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQWEsQ0FBQTtZQUM1QyxLQUFLLE1BQU0sR0FBRyxJQUFJLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDaEMsSUFBSSxNQUFNLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRSxDQUFDO29CQUM3RCxTQUFTLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUE7Z0JBQ2xELENBQUM7WUFDTCxDQUFDO1FBQ0wsQ0FBQzthQUFNLENBQUM7WUFDSixPQUFPLENBQUMsR0FBRyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxJQUFJLEVBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxDQUFBO1FBQ3BFLENBQUM7SUFDTCxDQUFDO0lBQ0QsT0FBTyxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUE7SUFDN0IsT0FBTyxPQUFPLENBQUE7QUFDbEIsQ0FBQyxDQUFBIn0=
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2NsaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9fY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxhQUFhLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBQyxVQUFVLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNsRixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUM3RCxPQUFPLEVBQUUsSUFBSSxJQUFJLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFBO0FBQ3BELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUV4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxFQUFFO0lBQ3pCLE1BQU0sY0FBYyxHQUFHLE1BQU0sQ0FBQztJQUM5QixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzVCLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBQ3RDLENBQUM7SUFDRCxPQUFPLENBQUMsRUFBRSxDQUFDLG9CQUFvQixFQUFFLENBQUMsTUFBYyxFQUFFLEVBQUU7UUFDaEQsT0FBTyxDQUFDLEtBQUssQ0FBQywrQkFBK0IsRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUMxRCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQTtBQUVELE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxDQUFDLElBQVMsRUFBbUIsRUFBRTtJQUVuRCxNQUFNLE9BQU8sR0FBbUI7UUFDNUIsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsR0FBRyxFQUFFLElBQUksQ0FBQyxHQUFHO1FBQ2IsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1FBQ2pCLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLE9BQU8s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
|
||||
File diff suppressed because one or more lines are too long
@ -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
@ -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
2
packages/media/dist-in/types.d.ts
vendored
2
packages/media/dist-in/types.d.ts
vendored
@ -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;
|
||||
|
||||
@ -1 +0,0 @@
|
||||
mock-png-data
|
||||
4963
packages/media/package-lock.json
generated
4963
packages/media/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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'",
|
||||
|
||||
56
packages/media/salamand.json
Normal file
56
packages/media/salamand.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
95
packages/media/scripts/nexe.js
Normal file
95
packages/media/scripts/nexe.js
Normal 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);
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)`)
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +47,8 @@ export type IResizeOptions = IOptions & IResizeOptionsSharp & {
|
||||
minWidth?: number
|
||||
minHeight?: number
|
||||
minSize?: number
|
||||
square?: boolean
|
||||
fillColor?: string
|
||||
|
||||
}
|
||||
|
||||
|
||||
BIN
packages/media/tests/images/out_jpg/DSC01325_sq.jpg
Normal file
BIN
packages/media/tests/images/out_jpg/DSC01325_sq.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
BIN
packages/media/tests/images/out_jpg/DSC01354_sq.jpg
Normal file
BIN
packages/media/tests/images/out_jpg/DSC01354_sq.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 298 KiB |
BIN
packages/media/tests/images/out_jpg/DSC01357_sq.jpg
Normal file
BIN
packages/media/tests/images/out_jpg/DSC01357_sq.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 372 KiB |
Loading…
Reference in New Issue
Block a user