@public-tauri/raycast-convert 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -36
- package/dist/cli.mjs +2 -3
- package/dist/index.mjs +2 -2
- package/dist/src-Re0YIJP-.mjs +729 -0
- package/package.json +8 -3
- package/src/cli.ts +0 -1
- package/src/commands.ts +54 -5
- package/src/files.ts +26 -4
- package/src/generate/copy-templates.ts +88 -0
- package/src/generate/server-module.ts +132 -24
- package/src/generate/tsdown-config.ts +26 -10
- package/src/index.ts +124 -26
- package/src/options.ts +35 -2
- package/src/package-json.ts +18 -10
- package/src/package-name.ts +40 -0
- package/src/types.ts +24 -2
- package/templates/host-instance.ts +35 -0
- package/templates/json-patch.ts +166 -0
- package/templates/raycast-view-protocol.ts +43 -0
- package/templates/raycast-worker-runtime.ts +240 -0
- package/templates/virtual-serialize.ts +215 -0
- package/tsconfig.json +17 -0
- package/dist/src-DPoXoCnp.mjs +0 -414
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@public-tauri/raycast-convert",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Convert Raycast extensions into Public Tauri plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
8
|
"src",
|
|
9
|
+
"templates",
|
|
10
|
+
"tsconfig.json",
|
|
9
11
|
"README.md",
|
|
10
12
|
"package.json"
|
|
11
13
|
],
|
|
@@ -22,8 +24,10 @@
|
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
26
|
"@types/node": "^24.1.0",
|
|
27
|
+
"@types/react": "^19.2.14",
|
|
28
|
+
"@types/react-reconciler": "^0.33.0",
|
|
25
29
|
"eslint": "^9.32.0",
|
|
26
|
-
"tsdown": "^0.
|
|
30
|
+
"tsdown": "^0.22.1",
|
|
27
31
|
"typescript": "~5.9.2"
|
|
28
32
|
},
|
|
29
33
|
"publishConfig": {
|
|
@@ -33,6 +37,7 @@
|
|
|
33
37
|
"scripts": {
|
|
34
38
|
"build": "tsdown",
|
|
35
39
|
"convert": "tsx src/cli.ts",
|
|
36
|
-
"lint": "eslint src"
|
|
40
|
+
"lint": "eslint src",
|
|
41
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
37
42
|
}
|
|
38
43
|
}
|
package/src/cli.ts
CHANGED
package/src/commands.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { exists } from './files';
|
|
3
|
-
import type { ConvertedCommand, RaycastCommand } from './types';
|
|
3
|
+
import type { ConvertedCommand, RaycastCommand, RaycastCommandArgument } from './types';
|
|
4
4
|
|
|
5
5
|
const findCommandEntry = async (inputDir: string, command: RaycastCommand) => {
|
|
6
6
|
const candidates = [
|
|
@@ -19,26 +19,75 @@ const findCommandEntry = async (inputDir: string, command: RaycastCommand) => {
|
|
|
19
19
|
return null;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const SUPPORTED_COMMAND_MODES = new Set(['no-view', 'view']);
|
|
23
|
+
const SUPPORTED_COMMAND_ARGUMENT_TYPES = new Set(['text', 'password', 'dropdown']);
|
|
24
|
+
|
|
25
|
+
const isObject = (value: unknown): value is Record<string, unknown> => (
|
|
26
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const validateCommandArguments = (command: RaycastCommand): string | null => {
|
|
30
|
+
const args = command.arguments;
|
|
31
|
+
if (!args) return null;
|
|
32
|
+
if (!Array.isArray(args)) return 'arguments must be an array';
|
|
33
|
+
if (args.length > 3) return 'arguments length exceeds Raycast limit (3)';
|
|
34
|
+
|
|
35
|
+
for (const item of args) {
|
|
36
|
+
const arg = item as RaycastCommandArgument;
|
|
37
|
+
if (!isObject(arg)) return 'argument item must be an object';
|
|
38
|
+
if (!arg.name || typeof arg.name !== 'string') return 'argument.name is required';
|
|
39
|
+
if (!arg.type || typeof arg.type !== 'string' || !SUPPORTED_COMMAND_ARGUMENT_TYPES.has(arg.type)) {
|
|
40
|
+
return `argument "${arg.name}" has unsupported type: ${String(arg.type)}`;
|
|
41
|
+
}
|
|
42
|
+
if (arg.required !== undefined && typeof arg.required !== 'boolean') {
|
|
43
|
+
return `argument "${arg.name}" has invalid required field`;
|
|
44
|
+
}
|
|
45
|
+
if (arg.placeholder !== undefined && typeof arg.placeholder !== 'string') {
|
|
46
|
+
return `argument "${arg.name}" has invalid placeholder field`;
|
|
47
|
+
}
|
|
48
|
+
if (arg.type === 'dropdown') {
|
|
49
|
+
if (!Array.isArray(arg.data) || arg.data.length === 0) {
|
|
50
|
+
return `dropdown argument "${arg.name}" requires non-empty data`;
|
|
51
|
+
}
|
|
52
|
+
for (const option of arg.data) {
|
|
53
|
+
if (!isObject(option) || typeof option.value !== 'string') {
|
|
54
|
+
return `dropdown argument "${arg.name}" has invalid option value`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const resolveSupportedCommands = async (inputDir: string, sourceCommands: RaycastCommand[]) => {
|
|
23
63
|
const convertedCommands: ConvertedCommand[] = [];
|
|
24
64
|
const skippedCommands: { name: string, reason: string }[] = [];
|
|
25
65
|
|
|
26
66
|
for (const command of sourceCommands) {
|
|
27
|
-
|
|
67
|
+
const mode = command.mode || 'view';
|
|
68
|
+
if (!SUPPORTED_COMMAND_MODES.has(mode)) {
|
|
28
69
|
skippedCommands.push({ name: command.name, reason: `Unsupported mode: ${command.mode || '<empty>'}` });
|
|
29
70
|
continue;
|
|
30
71
|
}
|
|
72
|
+
const argumentErr = validateCommandArguments(command);
|
|
73
|
+
if (argumentErr) {
|
|
74
|
+
skippedCommands.push({ name: command.name, reason: `Invalid arguments: ${argumentErr}` });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
31
77
|
const entry = await findCommandEntry(inputDir, command);
|
|
32
78
|
if (!entry) {
|
|
33
79
|
skippedCommands.push({ name: command.name, reason: 'Command entry not found under src/' });
|
|
34
80
|
continue;
|
|
35
81
|
}
|
|
36
|
-
convertedCommands.push({ ...command, entry });
|
|
82
|
+
convertedCommands.push({ ...command, mode: mode as ConvertedCommand['mode'], entry });
|
|
37
83
|
}
|
|
38
84
|
|
|
39
85
|
if (!convertedCommands.length) {
|
|
40
|
-
throw new Error('No Raycast
|
|
86
|
+
throw new Error('No supported Raycast commands were converted');
|
|
41
87
|
}
|
|
42
88
|
|
|
43
89
|
return { convertedCommands, skippedCommands };
|
|
44
90
|
};
|
|
91
|
+
|
|
92
|
+
/** @deprecated Use {@link resolveSupportedCommands} */
|
|
93
|
+
export const resolveNoViewCommands = resolveSupportedCommands;
|
package/src/files.ts
CHANGED
|
@@ -17,8 +17,30 @@ export const exists = async (filePath: string) => {
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
/** Names skipped when mirroring the Raycast plugin into outDir (see README). */
|
|
21
|
+
export const PLUGIN_COPY_SKIP_NAMES = new Set([
|
|
22
|
+
'package.json',
|
|
23
|
+
'node_modules',
|
|
24
|
+
'pnpm-lock.yaml',
|
|
25
|
+
'package-lock.json',
|
|
26
|
+
'yarn.lock',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Recursively copy the Raycast plugin directory into `outputDir`, except entries in `skipNames`.
|
|
31
|
+
* Source `package.json` is never copied; generated `package.json` is written separately.
|
|
32
|
+
*/
|
|
33
|
+
export const copyPluginSourceToOutput = async (
|
|
34
|
+
inputDir: string,
|
|
35
|
+
outputDir: string,
|
|
36
|
+
skipNames: ReadonlySet<string> = PLUGIN_COPY_SKIP_NAMES,
|
|
37
|
+
) => {
|
|
38
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
39
|
+
const entries = await fs.readdir(inputDir, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (skipNames.has(entry.name)) continue;
|
|
42
|
+
const from = path.join(inputDir, entry.name);
|
|
43
|
+
const to = path.join(outputDir, entry.name);
|
|
44
|
+
await fs.cp(from, to, { recursive: true });
|
|
45
|
+
}
|
|
24
46
|
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const templateDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../templates');
|
|
7
|
+
|
|
8
|
+
const workerViewRelPaths = [
|
|
9
|
+
'json-patch.ts',
|
|
10
|
+
'raycast-view-protocol.ts',
|
|
11
|
+
'raycast-worker-runtime.ts',
|
|
12
|
+
'host-instance.ts',
|
|
13
|
+
'virtual-serialize.ts',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
function copyTemplateFile(relativePath: string, buildDir: string) {
|
|
17
|
+
const dest = path.join(buildDir, relativePath);
|
|
18
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
19
|
+
fs.copyFileSync(path.join(templateDir, relativePath), dest);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 自 startDir 向上查找 monorepo 根(与 options.ts 一致) */
|
|
23
|
+
export function findPublicTauriRepoRoot(startDir: string): string | null {
|
|
24
|
+
let dir = path.resolve(startDir);
|
|
25
|
+
const { root } = path.parse(dir);
|
|
26
|
+
while (dir !== root) {
|
|
27
|
+
const ws = path.join(dir, 'pnpm-workspace.yaml');
|
|
28
|
+
const apiPkg = path.join(dir, 'packages', 'api', 'package.json');
|
|
29
|
+
if (fs.existsSync(ws) && fs.existsSync(apiPkg)) {
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
dir = path.dirname(dir);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getTemplateRaycastViewDistDir(repoRoot: string): string {
|
|
38
|
+
return path.join(repoRoot, 'packages/template/dist');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 若尚未 build,则在 repo 根执行 `pnpm --filter @public-tauri/template build` */
|
|
42
|
+
export function ensureRaycastViewTemplateBuilt(repoRoot: string): void {
|
|
43
|
+
const marker = path.join(getTemplateRaycastViewDistDir(repoRoot), 'raycast.html');
|
|
44
|
+
if (fs.existsSync(marker)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const result = spawnSync(
|
|
48
|
+
'pnpm',
|
|
49
|
+
['--filter', '@public-tauri/template', 'run', 'build'],
|
|
50
|
+
{
|
|
51
|
+
cwd: repoRoot,
|
|
52
|
+
stdio: 'inherit',
|
|
53
|
+
shell: process.platform === 'win32',
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
if (result.status !== 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'Building @public-tauri/template failed. From the repo root run: pnpm --filter @public-tauri/template build',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 将 `packages/template` 的 Vite 应用产物复制到插件 `dist/view/`(与 `dist/server.js` 并列)。
|
|
65
|
+
* wujie `publicPlugin.html` 指向 `./dist/view/raycast.html`。
|
|
66
|
+
*/
|
|
67
|
+
export function copyRaycastViewTemplateAppDist(repoRoot: string, pluginDistDir: string): void {
|
|
68
|
+
const srcDir = getTemplateRaycastViewDistDir(repoRoot);
|
|
69
|
+
const marker = path.join(srcDir, 'raycast.html');
|
|
70
|
+
if (!fs.existsSync(marker)) {
|
|
71
|
+
throw new Error(`Missing ${marker}; build @public-tauri/template first.`);
|
|
72
|
+
}
|
|
73
|
+
const destDir = path.join(pluginDistDir, 'view');
|
|
74
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
75
|
+
fs.cpSync(srcDir, destDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 将 Worker 侧 view 模板(reconciler + 宿主类型 + 序列化 + 组件)复制到 `.raycast-build` */
|
|
79
|
+
export function copyRaycastWorkerViewBundle(buildDir: string) {
|
|
80
|
+
for (const rel of workerViewRelPaths) {
|
|
81
|
+
copyTemplateFile(rel, buildDir);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @deprecated 使用 {@link copyRaycastWorkerViewBundle};保留签名以兼容旧调用(按 dest 所在目录复制整套 Worker 模板) */
|
|
86
|
+
export const copyRaycastWorkerRuntimeTemplate = (destAbsolutePath: string) => {
|
|
87
|
+
copyRaycastWorkerViewBundle(path.dirname(destAbsolutePath));
|
|
88
|
+
};
|
|
@@ -1,50 +1,59 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import type { ConvertedCommand } from '../types';
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
+
const commandEntryImportSpecifier = (
|
|
5
|
+
inputDir: string,
|
|
6
|
+
outputDir: string,
|
|
7
|
+
buildDir: string,
|
|
8
|
+
entry: string,
|
|
9
|
+
) => {
|
|
10
|
+
const outputEntry = path.join(outputDir, path.relative(path.resolve(inputDir), path.resolve(entry)));
|
|
11
|
+
let rel = path.relative(path.resolve(buildDir), outputEntry);
|
|
12
|
+
rel = rel.split(path.sep).join('/');
|
|
13
|
+
if (rel && !rel.startsWith('.') && !rel.startsWith('/')) {
|
|
14
|
+
return `./${rel}`;
|
|
15
|
+
}
|
|
16
|
+
return rel;
|
|
17
|
+
};
|
|
4
18
|
|
|
5
19
|
export const generateServerModule = (
|
|
6
|
-
commands: ConvertedCommand[],
|
|
20
|
+
commands: { noView: ConvertedCommand[]; view: ConvertedCommand[] },
|
|
7
21
|
packageName: string,
|
|
8
22
|
publicCommands: Record<string, unknown>[],
|
|
23
|
+
layout: { inputDir: string, outputDir: string, buildDir: string },
|
|
9
24
|
) => {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const commandManifests = JSON.stringify(publicCommands, null, 2);
|
|
13
|
-
return `import path from 'node:path';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { channel } from '@public-tauri/api/node';
|
|
16
|
-
import { __setRaycastContext } from '@public-tauri/api/raycast';
|
|
25
|
+
const nvLoaders = commands.noView.map(command => ` ${JSON.stringify(command.name)}: () => import(${JSON.stringify(commandEntryImportSpecifier(layout.inputDir, layout.outputDir, layout.buildDir, command.entry))}),`).join('\n');
|
|
26
|
+
const vvLoaders = commands.view.map(command => ` ${JSON.stringify(command.name)}: () => import(${JSON.stringify(commandEntryImportSpecifier(layout.inputDir, layout.outputDir, layout.buildDir, command.entry))}),`).join('\n');
|
|
17
27
|
|
|
18
|
-
|
|
28
|
+
const commandManifests = JSON.stringify(publicCommands, null, 2);
|
|
19
29
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
const viewRuntimeImport = commands.view.length
|
|
31
|
+
? 'import { createRaycastViewSession, __setRaycastViewContext } from \'./raycast-worker-runtime\';'
|
|
32
|
+
: '';
|
|
23
33
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const commandManifests = ${commandManifests};
|
|
27
|
-
|
|
28
|
-
channel.handle('raycast:run', async (payload = {}) => {
|
|
34
|
+
const raycastRunHandler = commands.noView.length
|
|
35
|
+
? `channel.handle('raycast:run', async (payload = {}) => {
|
|
29
36
|
const commandName = String(payload.commandName || '');
|
|
30
|
-
const
|
|
31
|
-
if (!
|
|
37
|
+
const loadCommandModule = commandModuleLoaders[commandName];
|
|
38
|
+
if (!loadCommandModule) {
|
|
32
39
|
throw new Error(\`Unknown Raycast command: \${commandName}\`);
|
|
33
40
|
}
|
|
34
|
-
const run = commandModule.default;
|
|
35
|
-
if (typeof run !== 'function') {
|
|
36
|
-
throw new Error(\`Raycast command \${commandName} has no default function export\`);
|
|
37
|
-
}
|
|
38
41
|
const launchPayload = payload.options?.payload || {};
|
|
39
42
|
__setRaycastContext({
|
|
40
43
|
pluginName: ${JSON.stringify(packageName)},
|
|
41
44
|
commandName,
|
|
45
|
+
commandMode: 'no-view',
|
|
42
46
|
commands: commandManifests,
|
|
43
47
|
launchType: launchPayload.launchType,
|
|
44
48
|
preferences: payload.preferences || {},
|
|
45
49
|
supportPath: path.join(pluginRoot, '.raycast-compat'),
|
|
46
50
|
assetsPath: path.join(pluginRoot, 'assets'),
|
|
47
51
|
});
|
|
52
|
+
const commandModule = await loadCommandModule();
|
|
53
|
+
const run = commandModule.default;
|
|
54
|
+
if (typeof run !== 'function') {
|
|
55
|
+
throw new Error(\`Raycast command \${commandName} has no default function export\`);
|
|
56
|
+
}
|
|
48
57
|
return await run({
|
|
49
58
|
arguments: launchPayload.arguments || {},
|
|
50
59
|
fallbackText: launchPayload.fallbackText ?? payload.query ?? '',
|
|
@@ -52,5 +61,104 @@ channel.handle('raycast:run', async (payload = {}) => {
|
|
|
52
61
|
launchType: launchPayload.launchType || 'userInitiated',
|
|
53
62
|
});
|
|
54
63
|
});
|
|
64
|
+
`
|
|
65
|
+
: '';
|
|
66
|
+
|
|
67
|
+
const viewHandlers = commands.view.length
|
|
68
|
+
? `const viewSessions = new Map();
|
|
69
|
+
|
|
70
|
+
channel.handle('raycast:view:mount', async (payload = {}) => {
|
|
71
|
+
const commandName = String(payload.commandName || '');
|
|
72
|
+
const existingSession = viewSessions.get(commandName);
|
|
73
|
+
if (existingSession) {
|
|
74
|
+
existingSession.unmount();
|
|
75
|
+
viewSessions.delete(commandName);
|
|
76
|
+
}
|
|
77
|
+
const loadViewCommandModule = viewCommandModuleLoaders[commandName];
|
|
78
|
+
if (!loadViewCommandModule) {
|
|
79
|
+
throw new Error(\`Unknown Raycast view command: \${commandName}\`);
|
|
80
|
+
}
|
|
81
|
+
__setRaycastViewContext({
|
|
82
|
+
pluginName: ${JSON.stringify(packageName)},
|
|
83
|
+
commandName,
|
|
84
|
+
preferences: payload.preferences || {},
|
|
85
|
+
supportPath: path.join(pluginRoot, '.raycast-compat'),
|
|
86
|
+
assetsPath: path.join(pluginRoot, 'assets'),
|
|
87
|
+
launchProps: {
|
|
88
|
+
arguments: payload.options?.payload?.arguments || {},
|
|
89
|
+
fallbackText: payload.options?.payload?.fallbackText ?? payload.query ?? '',
|
|
90
|
+
launchContext: payload.options?.payload?.context ?? payload,
|
|
91
|
+
launchType: payload.options?.payload?.launchType || 'userInitiated',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const commandModule = await loadViewCommandModule();
|
|
95
|
+
const Command = commandModule.default;
|
|
96
|
+
if (typeof Command !== 'function') {
|
|
97
|
+
throw new Error(\`Raycast view command \${commandName} has no default function export\`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const session = createRaycastViewSession({
|
|
101
|
+
emitSnapshot: (snapshot) => channel.emit('raycast:view:snapshot', snapshot),
|
|
102
|
+
emitPatch: (patches) => channel.emit('raycast:view:patch', patches),
|
|
103
|
+
});
|
|
104
|
+
viewSessions.set(commandName, session);
|
|
105
|
+
await session.mount(Command);
|
|
106
|
+
return true;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
channel.handle('raycast:view:run-action', async (payload = {}) => {
|
|
110
|
+
const session = viewSessions.get(String(payload.commandName || ''));
|
|
111
|
+
if (!session) throw new Error(\`No Raycast view session for \${String(payload.commandName || '')}\`);
|
|
112
|
+
const rawArgs = payload.args;
|
|
113
|
+
const args = Array.isArray(rawArgs) ? rawArgs : [];
|
|
114
|
+
await session.dispatchHostEvent(String(payload.hostId || ''), String(payload.event || 'onAction'), args);
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
channel.handle('raycast:view:unmount', async (payload = {}) => {
|
|
119
|
+
const commandName = String(payload.commandName || '');
|
|
120
|
+
const session = viewSessions.get(commandName);
|
|
121
|
+
if (session) {
|
|
122
|
+
session.unmount();
|
|
123
|
+
viewSessions.delete(commandName);
|
|
124
|
+
}
|
|
125
|
+
// 无活跃 view 会话时退出 Worker,配合宿主按需加载下次再拉起线程
|
|
126
|
+
if (viewSessions.size === 0) {
|
|
127
|
+
queueMicrotask(() => process.exit(0));
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
});
|
|
131
|
+
`
|
|
132
|
+
: '';
|
|
133
|
+
|
|
134
|
+
const nvMapBlock = commands.noView.length
|
|
135
|
+
? `const commandModuleLoaders: Record<string, () => Promise<any>> = {
|
|
136
|
+
${nvLoaders}
|
|
137
|
+
};
|
|
138
|
+
`
|
|
139
|
+
: 'const commandModuleLoaders: Record<string, () => Promise<any>> = {};';
|
|
140
|
+
|
|
141
|
+
const vvMapBlock = commands.view.length
|
|
142
|
+
? `const viewCommandModuleLoaders: Record<string, () => Promise<any>> = {
|
|
143
|
+
${vvLoaders}
|
|
144
|
+
};
|
|
145
|
+
`
|
|
146
|
+
: 'const viewCommandModuleLoaders: Record<string, () => Promise<any>> = {};';
|
|
147
|
+
|
|
148
|
+
return `import path from 'node:path';
|
|
149
|
+
import { fileURLToPath } from 'node:url';
|
|
150
|
+
import { channel } from '@public-tauri/api/node';
|
|
151
|
+
import { __setRaycastContext } from '@public-tauri/api/raycast';
|
|
152
|
+
${viewRuntimeImport}
|
|
153
|
+
|
|
154
|
+
${nvMapBlock}
|
|
155
|
+
${vvMapBlock}
|
|
156
|
+
|
|
157
|
+
const distDir = path.dirname(fileURLToPath(import.meta.url));
|
|
158
|
+
const pluginRoot = path.dirname(distDir);
|
|
159
|
+
const commandManifests = ${commandManifests};
|
|
160
|
+
|
|
161
|
+
${raycastRunHandler}
|
|
162
|
+
${viewHandlers}
|
|
55
163
|
`;
|
|
56
164
|
};
|
|
@@ -10,13 +10,23 @@ const formatAliasProperty = (aliases: Record<string, string>) => {
|
|
|
10
10
|
return entries ? ` alias: {\n${entries}\n },` : ' alias: {},';
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'
|
|
16
|
-
|
|
13
|
+
/** Server 入口:`@raycast/api` → `@public-tauri/api/raycast` 源码;view 与 no-view 一致。 */
|
|
14
|
+
const getServerAliases = (outputDir: string): Record<string, string> => {
|
|
15
|
+
const apiSrc = path.join(outputDir, 'node_modules', '@public-tauri', 'api', 'src');
|
|
16
|
+
return {
|
|
17
|
+
'@raycast/api': path.join(apiSrc, 'raycast.ts'),
|
|
18
|
+
'@public-tauri/api/node': path.join(apiSrc, 'node.ts'),
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const generateTsdownConfig = (
|
|
23
|
+
options: ResolvedConvertOptions,
|
|
24
|
+
flags: { hasPublicMain: boolean },
|
|
25
|
+
) => {
|
|
26
|
+
const entries: string[] = [];
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
{
|
|
28
|
+
if (flags.hasPublicMain) {
|
|
29
|
+
entries.push(` {
|
|
20
30
|
entry: ${JSON.stringify(path.join(options.buildDir, 'public-main.ts'))},
|
|
21
31
|
format: 'esm',
|
|
22
32
|
platform: 'browser',
|
|
@@ -27,8 +37,10 @@ export const generateTsdownConfig = (options: ResolvedConvertOptions) => `export
|
|
|
27
37
|
alwaysBundle: () => true,
|
|
28
38
|
},
|
|
29
39
|
${formatAliasProperty({})}
|
|
30
|
-
}
|
|
31
|
-
|
|
40
|
+
},`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
entries.push(` {
|
|
32
44
|
entry: ${JSON.stringify(path.join(options.buildDir, 'server.ts'))},
|
|
33
45
|
format: 'esm',
|
|
34
46
|
platform: 'node',
|
|
@@ -38,7 +50,11 @@ ${formatAliasProperty({})}
|
|
|
38
50
|
deps: {
|
|
39
51
|
alwaysBundle: () => true,
|
|
40
52
|
},
|
|
41
|
-
${formatAliasProperty(getServerAliases())}
|
|
42
|
-
}
|
|
53
|
+
${formatAliasProperty(getServerAliases(options.outputDir))}
|
|
54
|
+
},`);
|
|
55
|
+
|
|
56
|
+
return `export default [
|
|
57
|
+
${entries.join('\n')}
|
|
43
58
|
];
|
|
44
59
|
`;
|
|
60
|
+
};
|