Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 976c4d0980 | |||
| eda652994d | |||
| 46f82d2b65 | |||
| 90c5f07be4 |
17
changelog.md
17
changelog.md
@@ -1,5 +1,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-10-29 - 1.5.1 - fix(scriptindex)
|
||||||
|
Improve script search: use ObjectSorter with weighted results prioritizing slug and name
|
||||||
|
|
||||||
|
- Replaced smartfuzzy.FuzzyMatcher with smartfuzzy.ObjectSorter for multi-field fuzzy searching
|
||||||
|
- Search now runs across slug, name, and description fields
|
||||||
|
- Added weighting so slug matches are prioritized, name matches receive a medium boost
|
||||||
|
- Search returns ordered script objects based on adjusted score
|
||||||
|
|
||||||
|
## 2025-10-29 - 1.5.0 - feat(scripts)
|
||||||
|
Add fuzzy search and type filtering for community scripts; improve scripts CLI output and cache handling
|
||||||
|
|
||||||
|
- Integrate @push.rocks/smartfuzzy and use FuzzyMatcher to provide ranked, fuzzy search results for scripts
|
||||||
|
- Add optional type filtering to scripts search (e.g. type:vm, type:ct, type:pve) and parse a --filter option in the CLI
|
||||||
|
- Improve scripts CLI output: colored type badges, truncated descriptions, clearer usage/help text and filtered result messaging
|
||||||
|
- Optimize ScriptIndex.loadCache to avoid reloading when cache is already present
|
||||||
|
- Update deno.json to include the smartfuzzy dependency and export/import smartfuzzy in plugins
|
||||||
|
|
||||||
## 2025-10-28 - 1.4.2 - fix(scriptindex)
|
## 2025-10-28 - 1.4.2 - fix(scriptindex)
|
||||||
Handle missing script metadata fields in ScriptIndex.search to prevent crashes
|
Handle missing script metadata fields in ScriptIndex.search to prevent crashes
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/moxytool",
|
"name": "@serve.zone/moxytool",
|
||||||
"version": "1.4.2",
|
"version": "1.5.1",
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"nodeModulesDir": "auto",
|
"nodeModulesDir": "auto",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
"@push.rocks/smartpath": "npm:@push.rocks/smartpath@^5.0.5",
|
"@push.rocks/smartpath": "npm:@push.rocks/smartpath@^5.0.5",
|
||||||
"@push.rocks/smartshell": "npm:@push.rocks/smartshell@^3.2.2",
|
"@push.rocks/smartshell": "npm:@push.rocks/smartshell@^3.2.2",
|
||||||
"@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15",
|
"@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15",
|
||||||
|
"@push.rocks/smartfuzzy": "npm:@push.rocks/smartfuzzy@^2.0.0",
|
||||||
"@push.rocks/smartrx": "npm:@push.rocks/smartrx@^3.0.10",
|
"@push.rocks/smartrx": "npm:@push.rocks/smartrx@^3.0.10",
|
||||||
"@push.rocks/smartpromise": "npm:@push.rocks/smartpromise@^4.0.0",
|
"@push.rocks/smartpromise": "npm:@push.rocks/smartpromise@^4.0.0",
|
||||||
"@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.0",
|
"@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/moxytool",
|
"name": "@serve.zone/moxytool",
|
||||||
"version": "1.4.2",
|
"version": "1.5.1",
|
||||||
"description": "Proxmox administration tool for vGPU setup, VM management, and cluster configuration",
|
"description": "Proxmox administration tool for vGPU setup, VM management, and cluster configuration",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"proxmox",
|
"proxmox",
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/moxytool',
|
name: '@serve.zone/moxytool',
|
||||||
version: '1.4.2',
|
version: '1.5.1',
|
||||||
description: 'Proxmox administration tool for vGPU setup, VM management, and cluster configuration'
|
description: 'Proxmox administration tool for vGPU setup, VM management, and cluster configuration'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ export class ScriptIndex {
|
|||||||
*/
|
*/
|
||||||
public async loadCache(): Promise<void> {
|
public async loadCache(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
// Don't reload if already cached
|
||||||
|
if (this.cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Deno) {
|
if (!Deno) {
|
||||||
throw new Error('Deno runtime not available');
|
throw new Error('Deno runtime not available');
|
||||||
}
|
}
|
||||||
@@ -251,23 +256,51 @@ export class ScriptIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search scripts by query string
|
* Search scripts by query string with optional type filter
|
||||||
|
* @param query - Search query
|
||||||
|
* @param typeFilter - Optional type filter (e.g., 'vm', 'ct', 'pve')
|
||||||
*/
|
*/
|
||||||
public search(query: string): IScriptMetadata[] {
|
public search(query: string, typeFilter?: string): IScriptMetadata[] {
|
||||||
if (!this.cache) {
|
if (!this.cache) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowerQuery = query.toLowerCase();
|
let scripts = this.cache.scripts;
|
||||||
|
|
||||||
return this.cache.scripts.filter((script) => {
|
// Apply type filter if provided
|
||||||
// Search in name, description, and slug
|
if (typeFilter) {
|
||||||
return (
|
scripts = scripts.filter((script) => script.type === typeFilter);
|
||||||
(script.name && script.name.toLowerCase().includes(lowerQuery)) ||
|
}
|
||||||
(script.slug && script.slug.toLowerCase().includes(lowerQuery)) ||
|
|
||||||
(script.description && script.description.toLowerCase().includes(lowerQuery))
|
// Use ObjectSorter for fuzzy searching across multiple fields
|
||||||
);
|
const sorter = new plugins.smartfuzzy.ObjectSorter(scripts);
|
||||||
|
|
||||||
|
// Search across slug, name, and description
|
||||||
|
const results = sorter.sort(query, ['slug', 'name', 'description']);
|
||||||
|
|
||||||
|
// Post-process to weight results by which field matched
|
||||||
|
const weightedResults = results.map((result) => {
|
||||||
|
let weight = 1;
|
||||||
|
|
||||||
|
// Boost score if match was in slug (highest priority)
|
||||||
|
if (result.matches?.some((m) => m.key === 'slug')) {
|
||||||
|
weight = 3;
|
||||||
|
}
|
||||||
|
// Boost if match was in name (medium priority)
|
||||||
|
else if (result.matches?.some((m) => m.key === 'name')) {
|
||||||
|
weight = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
adjustedScore: (result.score || 0) / weight,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sort by adjusted score and return just the script objects
|
||||||
|
return weightedResults
|
||||||
|
.sort((a, b) => (a.adjustedScore || 0) - (b.adjustedScore || 0))
|
||||||
|
.map((result) => result.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -140,17 +140,8 @@ export const runCli = async () => {
|
|||||||
logger.log('info', '');
|
logger.log('info', '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get current version from deno.json
|
// Get current version from compile-time imported deno.json
|
||||||
const denoJsonPath = plugins.path.join(paths.packageDir, 'deno.json');
|
const currentVersion = denoConfig.version;
|
||||||
let currentVersion = '1.1.0'; // fallback
|
|
||||||
|
|
||||||
try {
|
|
||||||
const denoJsonContent = await Deno.readTextFile(denoJsonPath);
|
|
||||||
const denoJson = JSON.parse(denoJsonContent);
|
|
||||||
currentVersion = denoJson.version || currentVersion;
|
|
||||||
} catch {
|
|
||||||
// Use fallback version
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch latest version from Gitea API
|
// Fetch latest version from Gitea API
|
||||||
const apiUrl = 'https://code.foss.global/api/v1/repos/serve.zone/moxytool/releases/latest';
|
const apiUrl = 'https://code.foss.global/api/v1/repos/serve.zone/moxytool/releases/latest';
|
||||||
@@ -289,28 +280,50 @@ export const runCli = async () => {
|
|||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
logger.log('error', 'Please provide a search query');
|
logger.log('error', 'Please provide a search query');
|
||||||
logger.log('info', 'Usage: moxytool scripts search <query>');
|
logger.log('info', 'Usage: moxytool scripts search <query> [--filter type:vm]');
|
||||||
|
logger.log('info', 'Filters: type:vm, type:ct, type:pve, type:addon');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = scriptIndex.search(query as string);
|
// Parse filter option
|
||||||
|
let typeFilter: string | undefined;
|
||||||
|
if (argvArg.filter) {
|
||||||
|
const filterString = argvArg.filter as string;
|
||||||
|
if (filterString.startsWith('type:')) {
|
||||||
|
typeFilter = filterString.substring(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = scriptIndex.search(query as string, typeFilter);
|
||||||
|
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
logger.log('warn', `No scripts found matching "${query}"`);
|
const filterMsg = typeFilter ? ` (filtered by type:${typeFilter})` : '';
|
||||||
|
logger.log('warn', `No scripts found matching "${query}"${filterMsg}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('info', `Found ${results.length} script(s) matching "${query}":`);
|
const filterMsg = typeFilter ? ` \x1b[2m(filtered by type:${typeFilter})\x1b[0m` : '';
|
||||||
|
logger.log('info', `Found ${results.length} script(s) matching "\x1b[1m${query}\x1b[0m"${filterMsg}:`);
|
||||||
logger.log('info', '');
|
logger.log('info', '');
|
||||||
|
|
||||||
results.forEach(script => {
|
results.forEach(script => {
|
||||||
logger.log('info', `${script.slug} (${script.type})`);
|
const slug = script.slug || 'unknown';
|
||||||
logger.log('info', ` ${script.name}`);
|
const description = script.description ? script.description.substring(0, 100) : 'No description available';
|
||||||
logger.log('info', ` ${script.description.substring(0, 80)}...`);
|
|
||||||
|
// Type badge with colors
|
||||||
|
let typeBadge = '';
|
||||||
|
if (script.type === 'ct') typeBadge = '\x1b[36m[LXC]\x1b[0m';
|
||||||
|
else if (script.type === 'vm') typeBadge = '\x1b[35m[VM]\x1b[0m';
|
||||||
|
else if (script.type === 'pve') typeBadge = '\x1b[33m[PVE]\x1b[0m';
|
||||||
|
else typeBadge = `\x1b[2m[${script.type}]\x1b[0m`;
|
||||||
|
|
||||||
|
logger.log('info', `\x1b[1m\x1b[36m►\x1b[0m \x1b[1m${slug}\x1b[0m ${typeBadge}`);
|
||||||
|
logger.log('info', ` \x1b[2m${script.name}\x1b[0m`);
|
||||||
|
logger.log('info', ` ${description}${script.description && script.description.length > 100 ? '...' : ''}`);
|
||||||
logger.log('info', '');
|
logger.log('info', '');
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log('info', 'Use "moxytool scripts info <slug>" for more details');
|
logger.log('info', '\x1b[2mUse "moxytool scripts info <slug>" for more details\x1b[0m');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as projectinfo from '@push.rocks/projectinfo';
|
|||||||
import * as smartcli from '@push.rocks/smartcli';
|
import * as smartcli from '@push.rocks/smartcli';
|
||||||
import * as smartdelay from '@push.rocks/smartdelay';
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
|
import * as smartfuzzy from '@push.rocks/smartfuzzy';
|
||||||
import * as smartjson from '@push.rocks/smartjson';
|
import * as smartjson from '@push.rocks/smartjson';
|
||||||
import * as smartlog from '@push.rocks/smartlog';
|
import * as smartlog from '@push.rocks/smartlog';
|
||||||
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
|
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
|
||||||
@@ -21,6 +22,7 @@ export {
|
|||||||
smartcli,
|
smartcli,
|
||||||
smartdelay,
|
smartdelay,
|
||||||
smartfile,
|
smartfile,
|
||||||
|
smartfuzzy,
|
||||||
smartjson,
|
smartjson,
|
||||||
smartlog,
|
smartlog,
|
||||||
smartlogDestinationLocal,
|
smartlogDestinationLocal,
|
||||||
|
|||||||
Reference in New Issue
Block a user