猎豹Cheetah插件开发 - Alfred 篇
序
大家好,本系列第四篇为 Alfred 篇,介绍如何在 Cheetah for Alfred 1.0 的基础上引入前面完成开发的核心模块。
既然有了核心模块,也需要回过头来好好升级一下 Cheerah for Alfred 1.5 版本了~
本文仅阐述与 1.0
版本有差别的地方,少水一点 😄。
项目配置
引入核心模块
1.5
与 1.0
最大的区别就是引入了之前抽离的核心模块,替换掉 1.0
代码中与核心模块重合的逻辑。
npm install cheetah-core
# or
yarn add cheetah-core
将核心模块添加到依赖以后,就可以直接调用了。
types
不仅核心模块内有使用 txiki.js
的 API
,Alfred 项目内也有很多地方用到了 txiki.js
的 API
,这边引入了 txiki.js
的 types
定义,方便后续开发。
cheetah-for-alfred
├─ ...
├─ declares
│ ├─ ffi.d.ts
│ └─ txikijs.d.ts
├─ ...
tsconfig.json 中的 include 字段添加 declares 目录即可:
// tsconfig.json
{
...
"include": [
"src/**/*",
"declares/**/*" // 看这里
],
...
}
runtime
此次除了引入核心模块外,还对项目中的 txiki.js
可执行文件进行了升级,从原来的 v19.0.0-alpha
升级为 v22.4.1
,API 使用方面有一些区别,但是大同小异。
功能实现
Alfred 区别于 uTools 插件开发的方式,很多操作需要在 Workflows 中使用功能块处理,在流程关键位置使用 txiki.js
运行 js
文件(任意语言,只要有运行环境即可)完成处理后,结果使用指定格式输出到控制台,下一个流程即可获取使用。
1.5
与 1.0
一致,分别有以下三个 js
,在构建时需要有三个入口,rollup
配置如下:
import typescript from 'rollup-plugin-typescript';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import { uglify } from 'rollup-plugin-uglify';
const production = process.env.NODE_ENV === 'production';
const devInput = {
test: 'src/test.ts',
};
const productionInput = {
index: 'src/index.ts',
hits: 'src/hits.ts',
choose: 'src/choose.ts'
};
export default {
// 这里input要改成数组形式或者对象形式,对象形式可以修改打包的文件名,键对应的就是打包的文件名
input: production ? productionInput : devInput,
// 输出配置要改成拆分包的配置,以为多入口打包默认会执行打包拆分的特性。所以输出格式要改成amd
output: {
dir: 'dist',
format: 'esm'
},
plugins: [
resolve(),
commonjs(),
typescript(),
production && uglify(),
]
}
因为 Alfred Workflows 仅支持文件导出形式的发布,没有插件市场,这边可以使用混淆压缩,进一步减少导出的插件文件体积。
搜索项目并输出列表
搜索项目并输出列表的入口在 Script Filter
功能块中使用,输入框中接收到的值会传递到 $1
中,在 txiki.js
执行入口文件后可以作为运行时参数获取,用于筛选项目。
import { filterWithCache, filterWithSearchResult, Project } from 'cheetah-core';
import { output, initCore, errorHandle } from './utils';
import { ResultItem } from './types';
// 判断是否需要刷新缓存
const needRefresh: boolean = Array.from(tjs.args).includes('--r');
// 项目搜索关键词
const keyword: string = (Array.from(tjs.args).pop() as string) ?? '';
(async () => {
try {
initCore();
let projects: Project[] = await filterWithCache(keyword);
let fromCache = true;
// 如果缓存结果为空或者需要刷新缓存,则重新搜索
if (!projects.length || needRefresh) {
projects = await filterWithSearchResult(keyword);
fromCache = false;
}
const result: ResultItem[] = output(projects);
// 如果是从缓存中获取的内容,最后加上刷新的入口
if (fromCache) {
result.push({
title: '忽略缓存重新搜索',
subtitle: '以上结果从缓存中获得,选择本条将重新搜索项目并更新缓存',
arg: keyword,
valid: true,
icon: {
path: 'assets/refresh.png',
},
});
}
if (!result.length) {
result.push({
title: `没有找到名称包含 ${keyword} 的项目`,
subtitle: '请尝试更换关键词',
arg: keyword,
valid: false,
icon: {
path: 'assets/empty.png',
},
});
}
console.log(JSON.stringify({ items: result }));
} catch (error) {
errorHandle(error);
}
})()
基本逻辑没有改变,将原来项目内的项目搜索函数替换为核心模块提供的函数。
另添加了 initCore
函数用于初始化核心模块,添加 errorHandle
处理错误。
export function initCore() {
const workspaces = (getEnv('workspace') || '').split(/[,,]/);
const PlatformTypeMap = {
darwin: PlatformType.MAC,
windows: PlatformType.WINDOWS,
linux: PlatformType.LINUX,
};
let platformType: PlatformType =
PlatformTypeMap?.[tjs.platform] ?? PlatformType.MAC;
init({
cachePath,
workspaces: workspaces.join(','),
platformType, // Alfred 下没什么用
});
}
initCore
向核心模块注册了缓存文件位置,工作区配置,这样核心模块就能从 global
对象上获取并使用这两个参数了。
errorHandle
在下面详解。
打开项目并更新点击量
项目每一次被选择后其点击量都会加 1
,点击量越高,项目在搜索列表中的排序越前,方便用户下一次选择使用。
import { readCache, writeCache, getEnv, Project } from 'cheetah-core';
import { errorHandle, initCore } from './utils';
// 项目搜索关键词
const projectPath: string = (Array.from(tjs.args).pop() as string) ?? '';
const force = getEnv('force', '0') === '1';
(async () => {
try {
initCore();
// 获取路径对应的项目详情
const { cache: cacheList = [], editor } = await readCache();
const targetProject = cacheList.find(
(item: Project) => item.path === projectPath
);
if (!targetProject) {
return;
}
// 更新点击量
targetProject.hits += 1;
await writeCache(cacheList);
const { idePath, type } = targetProject;
// 自定义编辑器覆盖默认环境变量中配置的编辑器
const priorityIdePath = force
? tjs.getenv('idePath')
: idePath || editor[type] || tjs.getenv('idePath');
// 输出两个参数,以英文逗号分割
console.log([projectPath, priorityIdePath].join(','));
} catch (error: any) {
errorHandle(error, true);
}
})();
上面的代码主要是引入了核心模块中的缓存读写函数,读取到项目信息以后,给点击量加 1
,然后重新写入缓存。
完成点击量增加操作后,获取当前环境变量中的默认应用 idePath
,如果环境变量中 force
为 1
则取 环境变量中 idePath
的值,否则将按照下面的顺序决定打开项目的应用:
缓存文件内项目中配置的idePath > 缓存文件内配置的项目类型应用 > 环境变量中idePath
的值。
获取到项目绝对路径以及打开应用的名称后,即可使用 Run Script
功能块执行打开命令了:
open -a "$idePath" {query}
为项目指定应用
与 uTools 版插件一致,也添加了一个为项目快速配置编辑器的入口。 同样是使用了核心模块提供的缓存读写函数,在搜索后选择项目后即可打开文件选择窗口,选择想要为项目配置的应用。
import { readCache, writeCache, getEnv, Project } from 'cheetah-core';
import path from 'path-browserify';
const projectPath = getEnv('projectPath');
import { errorHandle, initCore } from './utils';
(async () => {
try {
initCore();
// 项目搜索关键词
let idePath: string = (Array.from(tjs.args).pop() as string) ?? '';
idePath = idePath.split(',')?.[0] ?? '';
if (!idePath) return;
idePath = path.basename(idePath);
// 获取路径对应的项目详情
const { cache: cacheList = [] } = await readCache();
const targetProject = cacheList.find(
(item: Project) => item.path === projectPath
);
if (!targetProject) {
return;
}
// 更新编辑器
targetProject.idePath = idePath;
await writeCache(cacheList);
} catch (error: any) {
errorHandle(error, true);
}
})();
因为 Alfred Workfows 功能块间传输数据时会替换上一个模块的输出,所以要将项目绝对路径先存在运行时的环境变量内,在入口文件执行时再取出使用,选择的应用名称则作为运行时参数使用。
获取到项目绝对路径与应用名称后即可写入缓存完成配置了。
错误处理
项目中创建了一个错误处理函数如下:
export function errorHandle(error: any, notice: boolean = false) {
const errorCode: number = error.message;
const errorMessage = ErrorCodeMessage[errorCode];
if (notice) {
console.log(`Error:${errorMessage}`);
return
}
console.log(
JSON.stringify({
items: [
{
title: 'Error',
subtitle: errorMessage,
arg: '',
valid: false,
icon: {
path: 'assets/empty.png',
},
},
],
})
);
}
在 Script Filter
功能块中报错为直接输出到列表:
如果需要系统通知的话,需要 Workflows 的功能块配合,这边直接输出报错信息即可,后续处理过程如下:
新建判断功能块,如果输入内容以 Error
开头则判断为报错信息,后面连接一个参数拆分器,其作用与 js 中的split
类似。
最后连接发送系统提示功能块,text
输入框为上一个功能块处理完的结果,取第二个参数。
构建发布
Alfred Workflows 的发布比较简单,项目构建完以后直接在 Alfred 中右键插件名称,在菜单中选择 Export
,后在导出面板中填写插件信息,插件描述等信息,点击 Export
后选择一个存放路径就完成了。
小结
至此 Cheetah for Alfred 1.5 的开发、发布已经完成,相信各位看官已经对 Alfred Workflows 开发有一定的了解了,可以尝试创建一些工作流,代替自己完成一些繁复机械的工作。
下一篇将介绍 Cheetah for Raycast 的开发,敬请期待~