@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.
@@ -1,414 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { spawnSync } from "node:child_process";
4
- //#region src/build.ts
5
- const installCommand = "pnpm";
6
- const installArgs = [
7
- "install",
8
- "--ignore-scripts",
9
- "--frozen-lockfile=false"
10
- ];
11
- const runInstall = (cwd, label) => {
12
- console.log(`Installing ${label} dependencies...`);
13
- const result = spawnSync(installCommand, installArgs, {
14
- cwd,
15
- stdio: "inherit"
16
- });
17
- if (result.status !== 0) throw new Error(`${label} dependency install failed with exit code ${result.status}`);
18
- };
19
- const installOutputDependencies = (options) => {
20
- runInstall(options.outputDir, "converted plugin");
21
- };
22
- const getBuildCommand = (options) => {
23
- return {
24
- command: "pnpm",
25
- args: [
26
- "exec",
27
- "tsdown",
28
- "--config",
29
- path.join(options.outputDir, "tsdown.config.ts")
30
- ],
31
- cwd: options.outputDir
32
- };
33
- };
34
- const buildConvertedPlugin = (options) => {
35
- const { command, args: buildArgs, cwd } = getBuildCommand(options);
36
- const result = spawnSync(command, buildArgs, {
37
- cwd,
38
- stdio: "inherit"
39
- });
40
- if (result.status !== 0) throw new Error(`Build failed with exit code ${result.status}`);
41
- };
42
- const installAndBuild = (options) => {
43
- installOutputDependencies(options);
44
- buildConvertedPlugin(options);
45
- };
46
- //#endregion
47
- //#region src/files.ts
48
- const readJson = async (filePath) => JSON.parse(await fs.readFile(filePath, "utf8"));
49
- const writeJson = async (filePath, value) => {
50
- await fs.mkdir(path.dirname(filePath), { recursive: true });
51
- await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
52
- };
53
- const exists = async (filePath) => {
54
- try {
55
- await fs.access(filePath);
56
- return true;
57
- } catch {
58
- return false;
59
- }
60
- };
61
- const copyAssetsDir = async (inputDir, assetsDir) => {
62
- const source = path.join(inputDir, "assets");
63
- if (!await exists(source)) return;
64
- await fs.cp(source, assetsDir, { recursive: true });
65
- };
66
- //#endregion
67
- //#region src/commands.ts
68
- const findCommandEntry = async (inputDir, command) => {
69
- const candidates = [
70
- path.join(inputDir, "src", `${command.name}.tsx`),
71
- path.join(inputDir, "src", `${command.name}.ts`),
72
- path.join(inputDir, "src", `${command.name}.jsx`),
73
- path.join(inputDir, "src", `${command.name}.js`),
74
- path.join(inputDir, "src", command.name, "index.tsx"),
75
- path.join(inputDir, "src", command.name, "index.ts"),
76
- path.join(inputDir, "src", command.name, "index.jsx"),
77
- path.join(inputDir, "src", command.name, "index.js")
78
- ];
79
- for (const candidate of candidates) if (await exists(candidate)) return candidate;
80
- return null;
81
- };
82
- const resolveNoViewCommands = async (inputDir, sourceCommands) => {
83
- const convertedCommands = [];
84
- const skippedCommands = [];
85
- for (const command of sourceCommands) {
86
- if (command.mode !== "no-view") {
87
- skippedCommands.push({
88
- name: command.name,
89
- reason: `Unsupported mode: ${command.mode || "<empty>"}`
90
- });
91
- continue;
92
- }
93
- const entry = await findCommandEntry(inputDir, command);
94
- if (!entry) {
95
- skippedCommands.push({
96
- name: command.name,
97
- reason: "Command entry not found under src/"
98
- });
99
- continue;
100
- }
101
- convertedCommands.push({
102
- ...command,
103
- entry
104
- });
105
- }
106
- if (!convertedCommands.length) throw new Error("No Raycast no-view commands were converted");
107
- return {
108
- convertedCommands,
109
- skippedCommands
110
- };
111
- };
112
- //#endregion
113
- //#region src/generate/public-main.ts
114
- const generatePublicMain = () => `export default function createPlugin() {
115
- const getApi = () => window.$wujie?.props;
116
- return {
117
- async onAction(command, action, query, options) {
118
- const api = getApi();
119
- try {
120
- await api.channel.invoke('raycast:run', {
121
- commandName: command.name,
122
- query,
123
- action,
124
- options,
125
- preferences: api.getPreferences?.() || {},
126
- });
127
- } catch (error) {
128
- const message = error instanceof Error ? error.message : String(error);
129
- await api.dialog.showToast(message);
130
- throw error;
131
- }
132
- },
133
- };
134
- }
135
- `;
136
- //#endregion
137
- //#region src/generate/server-module.ts
138
- const toImportName = (index) => `command${index}`;
139
- const generateServerModule = (commands, packageName, publicCommands) => {
140
- return `import path from 'node:path';
141
- import { fileURLToPath } from 'node:url';
142
- import { channel } from '@public-tauri/api/node';
143
- import { __setRaycastContext } from '@public-tauri/api/raycast';
144
-
145
- ${commands.map((command, index) => `import * as ${toImportName(index)} from ${JSON.stringify(command.entry)};`).join("\n")}
146
-
147
- const commandModules: Record<string, any> = {
148
- ${commands.map((command, index) => ` ${JSON.stringify(command.name)}: ${toImportName(index)},`).join("\n")}
149
- };
150
-
151
- const distDir = path.dirname(fileURLToPath(import.meta.url));
152
- const pluginRoot = path.dirname(distDir);
153
- const commandManifests = ${JSON.stringify(publicCommands, null, 2)};
154
-
155
- channel.handle('raycast:run', async (payload = {}) => {
156
- const commandName = String(payload.commandName || '');
157
- const commandModule = commandModules[commandName];
158
- if (!commandModule) {
159
- throw new Error(\`Unknown Raycast command: \${commandName}\`);
160
- }
161
- const run = commandModule.default;
162
- if (typeof run !== 'function') {
163
- throw new Error(\`Raycast command \${commandName} has no default function export\`);
164
- }
165
- const launchPayload = payload.options?.payload || {};
166
- __setRaycastContext({
167
- pluginName: ${JSON.stringify(packageName)},
168
- commandName,
169
- commands: commandManifests,
170
- launchType: launchPayload.launchType,
171
- preferences: payload.preferences || {},
172
- supportPath: path.join(pluginRoot, '.raycast-compat'),
173
- assetsPath: path.join(pluginRoot, 'assets'),
174
- });
175
- return await run({
176
- arguments: launchPayload.arguments || {},
177
- fallbackText: launchPayload.fallbackText ?? payload.query ?? '',
178
- launchContext: launchPayload.context ?? payload,
179
- launchType: launchPayload.launchType || 'userInitiated',
180
- });
181
- });
182
- `;
183
- };
184
- //#endregion
185
- //#region src/generate/tsdown-config.ts
186
- const formatAlias = (aliases) => Object.entries(aliases).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value)},`).join("\n");
187
- const formatAliasProperty = (aliases) => {
188
- const entries = formatAlias(aliases);
189
- return entries ? ` alias: {\n${entries}\n },` : " alias: {},";
190
- };
191
- const getServerAliases = () => ({
192
- "@raycast/api": "@public-tauri/api/raycast",
193
- "@raycast/utils": "@public-tauri/api/raycast/utils"
194
- });
195
- const generateTsdownConfig = (options) => `export default [
196
- {
197
- entry: ${JSON.stringify(path.join(options.buildDir, "public-main.ts"))},
198
- format: 'esm',
199
- platform: 'browser',
200
- target: 'es2022',
201
- outDir: ${JSON.stringify(options.distDir)},
202
- outExtensions: () => ({ js: '.js' }),
203
- deps: {
204
- alwaysBundle: () => true,
205
- },
206
- ${formatAliasProperty({})}
207
- },
208
- {
209
- entry: ${JSON.stringify(path.join(options.buildDir, "server.ts"))},
210
- format: 'esm',
211
- platform: 'node',
212
- target: 'es2022',
213
- outDir: ${JSON.stringify(options.distDir)},
214
- outExtensions: () => ({ js: '.js' }),
215
- deps: {
216
- alwaysBundle: () => true,
217
- },
218
- ${formatAliasProperty(getServerAliases())}
219
- },
220
- ];
221
- `;
222
- //#endregion
223
- //#region src/icons.ts
224
- const isUrlLike = (value) => /^(?:https?:|data:|asset:|public-icon:)/.test(value);
225
- const hasPathSegment = (value) => value.includes("/") || value.includes("\\");
226
- const normalizeRaycastIcon = (icon) => {
227
- if (!icon) return void 0;
228
- if (isUrlLike(icon)) return icon;
229
- if (icon.startsWith("./") || icon.startsWith("../") || icon.startsWith("/")) return icon;
230
- if (hasPathSegment(icon)) return `./${icon}`;
231
- return `./assets/${icon}`;
232
- };
233
- //#endregion
234
- //#region src/options.ts
235
- const resolveMode = (mode) => mode || "development";
236
- const resolveConvertOptions = (options) => {
237
- const inputDir = path.resolve(options.inputDir);
238
- const outputDir = path.resolve(options.outputDir || `${inputDir}-public`);
239
- const invocationDir = path.resolve(options.invocationDir || process.cwd());
240
- const mode = resolveMode(options.mode);
241
- return {
242
- inputDir,
243
- outputDir,
244
- build: Boolean(options.build),
245
- mode,
246
- invocationDir,
247
- publicApiDependency: mode === "development" ? `file:${path.join(invocationDir, "packages", "api")}` : "latest",
248
- buildDir: path.join(outputDir, ".raycast-build"),
249
- distDir: path.join(outputDir, "dist"),
250
- assetsDir: path.join(outputDir, "assets")
251
- };
252
- };
253
- //#endregion
254
- //#region src/package-json.ts
255
- const raycastApiPackages = new Set(["@raycast/api", "@raycast/utils"]);
256
- const rewriteDependencyMap = (dependencies) => {
257
- const rewritten = { ...dependencies || {} };
258
- let replacedRaycastApi = false;
259
- for (const packageName of raycastApiPackages) if (packageName in rewritten) {
260
- delete rewritten[packageName];
261
- replacedRaycastApi = true;
262
- }
263
- return {
264
- dependencies: rewritten,
265
- replacedRaycastApi
266
- };
267
- };
268
- const createConvertedPackage = (sourcePackage, publicPlugin, options) => {
269
- const dependenciesResult = rewriteDependencyMap(sourcePackage.dependencies);
270
- const devDependenciesResult = rewriteDependencyMap(sourcePackage.devDependencies);
271
- if (dependenciesResult.replacedRaycastApi || devDependenciesResult.replacedRaycastApi) options.warnings.push({
272
- type: "dependency",
273
- message: "Replaced @raycast/api and/or @raycast/utils with @public-tauri/api"
274
- });
275
- return {
276
- ...sourcePackage,
277
- version: sourcePackage.version || "1.0.0",
278
- type: "module",
279
- private: true,
280
- publicPlugin,
281
- scripts: {
282
- ...sourcePackage.scripts || {},
283
- build: "tsdown --config tsdown.config.ts"
284
- },
285
- dependencies: {
286
- ...dependenciesResult.dependencies,
287
- "@public-tauri/api": dependenciesResult.dependencies["@public-tauri/api"] || options.publicApiDependency
288
- },
289
- devDependencies: {
290
- ...devDependenciesResult.dependencies,
291
- tsdown: devDependenciesResult.dependencies.tsdown || "^0.21.7"
292
- }
293
- };
294
- };
295
- //#endregion
296
- //#region src/preferences.ts
297
- const mapPreferenceType = (type, warnings) => {
298
- switch (type) {
299
- case "password": return "password";
300
- case "textarea": return "textarea";
301
- case "dropdown": return "select";
302
- case "checkbox": return "select";
303
- case "textfield":
304
- case "appPicker":
305
- case void 0: return "text";
306
- default:
307
- warnings.push({
308
- type: "preference",
309
- message: `Unsupported preference type "${type}", converted to text`
310
- });
311
- return "text";
312
- }
313
- };
314
- const convertPreference = (preference, warnings) => {
315
- const type = mapPreferenceType(preference.type, warnings);
316
- const options = preference.type === "checkbox" ? [{
317
- label: "Yes",
318
- value: true
319
- }, {
320
- label: "No",
321
- value: false
322
- }] : preference.data?.map((item) => ({
323
- label: item.title || item.label || String(item.value),
324
- value: item.value
325
- }));
326
- return {
327
- name: preference.name,
328
- title: preference.title || preference.label || preference.name,
329
- description: preference.description,
330
- type,
331
- required: Boolean(preference.required),
332
- placeholder: preference.placeholder,
333
- defaultValue: preference.defaultValue ?? preference.default,
334
- ...options?.length ? { options } : {}
335
- };
336
- };
337
- const mergePreferences = (pluginPreferences, commandPreferences, warnings) => {
338
- const preferenceNames = /* @__PURE__ */ new Set();
339
- return [...pluginPreferences, ...commandPreferences].map((preference) => convertPreference(preference, warnings)).filter((preference) => {
340
- if (preferenceNames.has(preference.name)) {
341
- warnings.push({
342
- type: "preference",
343
- message: `Duplicate preference "${preference.name}" was skipped`
344
- });
345
- return false;
346
- }
347
- preferenceNames.add(preference.name);
348
- return true;
349
- });
350
- };
351
- //#endregion
352
- //#region src/index.ts
353
- const createPublicCommands = (commands, icon) => commands.map((command) => ({
354
- name: command.name,
355
- title: command.title || command.name,
356
- subtitle: command.subtitle || command.description,
357
- description: command.description,
358
- icon: normalizeRaycastIcon(command.icon) || icon,
359
- mode: "none",
360
- matches: [{
361
- type: "text",
362
- keywords: command.keywords?.length ? command.keywords : [command.title || command.name]
363
- }]
364
- }));
365
- const convertRaycastPlugin = async (rawOptions) => {
366
- const options = resolveConvertOptions(rawOptions);
367
- const warnings = [];
368
- const sourcePackage = await readJson(path.join(options.inputDir, "package.json"));
369
- const sourceCommands = sourcePackage.commands || [];
370
- const { convertedCommands, skippedCommands } = await resolveNoViewCommands(options.inputDir, sourceCommands);
371
- await fs.rm(options.outputDir, {
372
- recursive: true,
373
- force: true
374
- });
375
- await fs.mkdir(options.buildDir, { recursive: true });
376
- await fs.mkdir(options.distDir, { recursive: true });
377
- await copyAssetsDir(options.inputDir, options.assetsDir);
378
- const icon = normalizeRaycastIcon(sourcePackage.icon || convertedCommands[0]?.icon) || "extension";
379
- const commandPreferences = convertedCommands.flatMap((command) => command.preferences || []);
380
- const preferences = mergePreferences(sourcePackage.preferences || [], commandPreferences, warnings);
381
- const publicCommands = createPublicCommands(convertedCommands, icon);
382
- const publicPlugin = {
383
- title: sourcePackage.title || sourcePackage.name,
384
- subtitle: sourcePackage.description || sourcePackage.name,
385
- description: sourcePackage.description,
386
- icon,
387
- main: "./dist/public-main.js",
388
- server: "./dist/server.js",
389
- ...preferences.length ? { preferences } : {},
390
- commands: publicCommands
391
- };
392
- await writeJson(path.join(options.outputDir, "package.json"), createConvertedPackage(sourcePackage, publicPlugin, {
393
- publicApiDependency: options.publicApiDependency,
394
- warnings
395
- }));
396
- await fs.writeFile(path.join(options.buildDir, "public-main.ts"), generatePublicMain(), "utf8");
397
- await fs.writeFile(path.join(options.buildDir, "server.ts"), generateServerModule(convertedCommands, sourcePackage.name, publicCommands), "utf8");
398
- await fs.writeFile(path.join(options.outputDir, "tsdown.config.ts"), generateTsdownConfig(options), "utf8");
399
- const report = {
400
- source: options.inputDir,
401
- output: options.outputDir,
402
- convertedCommands: convertedCommands.map((command) => ({
403
- name: command.name,
404
- entry: command.entry
405
- })),
406
- skippedCommands,
407
- warnings
408
- };
409
- await writeJson(path.join(options.outputDir, "raycast-conversion-report.json"), report);
410
- if (options.build) installAndBuild(options);
411
- return report;
412
- };
413
- //#endregion
414
- export { convertRaycastPlugin as t };