@symbiosis-lab/moss-api 0.6.0 → 0.7.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/dist/index.d.mts +36 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +53 -3
- package/dist/index.mjs.map +1 -1
- package/dist/testing/index.mjs +16 -0
- package/dist/testing/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -471,6 +471,41 @@ interface DownloadResult {
|
|
|
471
471
|
* ```
|
|
472
472
|
*/
|
|
473
473
|
declare function fetchUrl(url: string, options?: FetchOptions): Promise<FetchResult>;
|
|
474
|
+
/**
|
|
475
|
+
* Options for HTTP POST requests
|
|
476
|
+
*/
|
|
477
|
+
interface PostOptions {
|
|
478
|
+
/** Timeout in milliseconds (default: 30000) */
|
|
479
|
+
timeoutMs?: number;
|
|
480
|
+
/** Additional headers */
|
|
481
|
+
headers?: Record<string, string>;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Perform an HTTP POST request with JSON body
|
|
485
|
+
*
|
|
486
|
+
* Uses Rust's HTTP client to bypass browser CORS restrictions.
|
|
487
|
+
* This is useful for OAuth flows and other API interactions.
|
|
488
|
+
*
|
|
489
|
+
* @param url - URL to POST to
|
|
490
|
+
* @param body - JSON object to send as the request body
|
|
491
|
+
* @param options - Optional configuration including timeout and headers
|
|
492
|
+
* @returns Fetch result with status, body, and helpers
|
|
493
|
+
* @throws Error if network request fails
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* // GitHub OAuth device code request
|
|
498
|
+
* const result = await httpPost(
|
|
499
|
+
* "https://github.com/login/device/code",
|
|
500
|
+
* { client_id: "xxx", scope: "repo workflow" },
|
|
501
|
+
* { headers: { Accept: "application/json" } }
|
|
502
|
+
* );
|
|
503
|
+
* if (result.ok) {
|
|
504
|
+
* const data = JSON.parse(result.text());
|
|
505
|
+
* }
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
declare function httpPost(url: string, body: Record<string, unknown>, options?: PostOptions): Promise<FetchResult>;
|
|
474
509
|
/**
|
|
475
510
|
* Download a URL and save directly to disk
|
|
476
511
|
*
|
|
@@ -1029,5 +1064,5 @@ declare function onEvent<T>(event: string, handler: (payload: T) => void): Promi
|
|
|
1029
1064
|
*/
|
|
1030
1065
|
declare function waitForEvent<T>(event: string, timeoutMs?: number): Promise<T>;
|
|
1031
1066
|
//#endregion
|
|
1032
|
-
export { AfterDeployContext, ArchType, ArchiveFormat, ArticleInfo, BaseContext, BeforeBuildContext, BinaryConfig, BinaryResolution, BinaryResolutionError, BinarySource, CompleteMessage, Cookie, DeploymentInfo, DialogResult, DnsTarget, DownloadOptions, DownloadResult, ErrorMessage, ExecuteOptions, ExecuteResult, ExtractOptions, ExtractResult, FetchOptions, FetchResult, GitHubSource, HookResult, LogMessage, OSType, OnBuildContext, OnDeployContext, PlatformInfo, PlatformKey, PluginCategory, PluginManifest, PluginMessage, ProgressMessage, ProjectInfo, ResolveBinaryOptions, ShowDialogOptions, SourceFiles, TauriCore, cancelDialog, clearPlatformCache, closeBrowser, downloadAsset, emitEvent, error, executeBinary, extractArchive, fetchUrl, fileExists, getMessageContext, getPlatformInfo, getPluginCookie, getTauriCore, isEventApiAvailable, isTauriAvailable, listFiles, listPluginFiles, log, makeExecutable, onEvent, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, resolveBinary, sendMessage, setMessageContext, setPluginCookie, showPluginDialog, submitDialogResult, waitForEvent, warn, writeFile, writePluginFile };
|
|
1067
|
+
export { AfterDeployContext, ArchType, ArchiveFormat, ArticleInfo, BaseContext, BeforeBuildContext, BinaryConfig, BinaryResolution, BinaryResolutionError, BinarySource, CompleteMessage, Cookie, DeploymentInfo, DialogResult, DnsTarget, DownloadOptions, DownloadResult, ErrorMessage, ExecuteOptions, ExecuteResult, ExtractOptions, ExtractResult, FetchOptions, FetchResult, GitHubSource, HookResult, LogMessage, OSType, OnBuildContext, OnDeployContext, PlatformInfo, PlatformKey, PluginCategory, PluginManifest, PluginMessage, PostOptions, ProgressMessage, ProjectInfo, ResolveBinaryOptions, ShowDialogOptions, SourceFiles, TauriCore, cancelDialog, clearPlatformCache, closeBrowser, downloadAsset, emitEvent, error, executeBinary, extractArchive, fetchUrl, fileExists, getMessageContext, getPlatformInfo, getPluginCookie, getTauriCore, httpPost, isEventApiAvailable, isTauriAvailable, listFiles, listPluginFiles, log, makeExecutable, onEvent, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, resolveBinary, sendMessage, setMessageContext, setPluginCookie, showPluginDialog, submitDialogResult, waitForEvent, warn, writeFile, writePluginFile };
|
|
1033
1068
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/plugin.ts","../src/types/context.ts","../src/types/hooks.ts","../src/types/messages.ts","../src/utils/tauri.ts","../src/utils/messaging.ts","../src/utils/logger.ts","../src/utils/browser.ts","../src/utils/filesystem.ts","../src/utils/plugin-storage.ts","../src/utils/http.ts","../src/utils/binary.ts","../src/utils/platform.ts","../src/utils/archive.ts","../src/utils/binary-resolver.ts","../src/utils/cookies.ts","../src/utils/window.ts","../src/utils/events.ts"],"sourcesContent":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/plugin.ts","../src/types/context.ts","../src/types/hooks.ts","../src/types/messages.ts","../src/utils/tauri.ts","../src/utils/messaging.ts","../src/utils/logger.ts","../src/utils/browser.ts","../src/utils/filesystem.ts","../src/utils/plugin-storage.ts","../src/utils/http.ts","../src/utils/binary.ts","../src/utils/platform.ts","../src/utils/archive.ts","../src/utils/binary-resolver.ts","../src/utils/cookies.ts","../src/utils/window.ts","../src/utils/events.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -433,6 +433,52 @@ async function fetchUrl(url, options = {}) {
|
|
|
433
433
|
};
|
|
434
434
|
}
|
|
435
435
|
/**
|
|
436
|
+
* Perform an HTTP POST request with JSON body
|
|
437
|
+
*
|
|
438
|
+
* Uses Rust's HTTP client to bypass browser CORS restrictions.
|
|
439
|
+
* This is useful for OAuth flows and other API interactions.
|
|
440
|
+
*
|
|
441
|
+
* @param url - URL to POST to
|
|
442
|
+
* @param body - JSON object to send as the request body
|
|
443
|
+
* @param options - Optional configuration including timeout and headers
|
|
444
|
+
* @returns Fetch result with status, body, and helpers
|
|
445
|
+
* @throws Error if network request fails
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```typescript
|
|
449
|
+
* // GitHub OAuth device code request
|
|
450
|
+
* const result = await httpPost(
|
|
451
|
+
* "https://github.com/login/device/code",
|
|
452
|
+
* { client_id: "xxx", scope: "repo workflow" },
|
|
453
|
+
* { headers: { Accept: "application/json" } }
|
|
454
|
+
* );
|
|
455
|
+
* if (result.ok) {
|
|
456
|
+
* const data = JSON.parse(result.text());
|
|
457
|
+
* }
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
async function httpPost(url, body, options = {}) {
|
|
461
|
+
const { timeoutMs = 3e4, headers = {} } = options;
|
|
462
|
+
const result = await getTauriCore().invoke("http_post", {
|
|
463
|
+
url,
|
|
464
|
+
body: JSON.stringify(body),
|
|
465
|
+
headers,
|
|
466
|
+
timeoutMs
|
|
467
|
+
});
|
|
468
|
+
const binaryString = atob(result.body_base64);
|
|
469
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
470
|
+
for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
471
|
+
return {
|
|
472
|
+
status: result.status,
|
|
473
|
+
ok: result.ok,
|
|
474
|
+
contentType: result.content_type,
|
|
475
|
+
body: bytes,
|
|
476
|
+
text() {
|
|
477
|
+
return new TextDecoder().decode(bytes);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
436
482
|
* Download a URL and save directly to disk
|
|
437
483
|
*
|
|
438
484
|
* Downloads the file and writes it directly to disk without passing
|
|
@@ -963,8 +1009,12 @@ async function downloadBinary(config, progress) {
|
|
|
963
1009
|
version = (await getLatestRelease(source.github.owner, source.github.repo)).version;
|
|
964
1010
|
const assetName = resolveAssetPattern(source.github.assetPattern, version, platform);
|
|
965
1011
|
downloadUrl = `https://github.com/${source.github.owner}/${source.github.repo}/releases/download/v${version}/${assetName}`;
|
|
966
|
-
} else if (source.directUrl)
|
|
967
|
-
|
|
1012
|
+
} else if (source.directUrl) {
|
|
1013
|
+
downloadUrl = source.directUrl;
|
|
1014
|
+
const versionMatch = downloadUrl.match(/[/v_](\d+\.\d+\.\d+)[/_]/);
|
|
1015
|
+
version = versionMatch ? versionMatch[1] : "unknown";
|
|
1016
|
+
progress("download", `Using direct download URL (v${version})...`);
|
|
1017
|
+
} else throw new BinaryResolutionError(`No download source configured for ${config.name}`, "download");
|
|
968
1018
|
progress("download", `Downloading ${config.name} v${version}...`);
|
|
969
1019
|
const ctx = getInternalContext();
|
|
970
1020
|
const archiveFilename = downloadUrl.split("/").pop() ?? "archive";
|
|
@@ -1361,5 +1411,5 @@ async function waitForEvent(event, timeoutMs = 3e4) {
|
|
|
1361
1411
|
}
|
|
1362
1412
|
|
|
1363
1413
|
//#endregion
|
|
1364
|
-
export { BinaryResolutionError, cancelDialog, clearPlatformCache, closeBrowser, downloadAsset, emitEvent, error, executeBinary, extractArchive, fetchUrl, fileExists, getMessageContext, getPlatformInfo, getPluginCookie, getTauriCore, isEventApiAvailable, isTauriAvailable, listFiles, listPluginFiles, log, makeExecutable, onEvent, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, resolveBinary, sendMessage, setMessageContext, setPluginCookie, showPluginDialog, submitDialogResult, waitForEvent, warn, writeFile, writePluginFile };
|
|
1414
|
+
export { BinaryResolutionError, cancelDialog, clearPlatformCache, closeBrowser, downloadAsset, emitEvent, error, executeBinary, extractArchive, fetchUrl, fileExists, getMessageContext, getPlatformInfo, getPluginCookie, getTauriCore, httpPost, isEventApiAvailable, isTauriAvailable, listFiles, listPluginFiles, log, makeExecutable, onEvent, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, resolveBinary, sendMessage, setMessageContext, setPluginCookie, showPluginDialog, submitDialogResult, waitForEvent, warn, writeFile, writePluginFile };
|
|
1365
1415
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["cachedPlatform: PlatformInfo | null","supportedPlatforms: PlatformKey[]","error","phase: \"detection\" | \"download\" | \"extraction\" | \"validation\"","cause?: Error","version: string | undefined","version: string","downloadUrl: string","error","cached: CachedRelease","unlisten: (() => void) | null","timeoutId: ReturnType<typeof setTimeout>"],"sources":["../src/utils/tauri.ts","../src/utils/messaging.ts","../src/utils/logger.ts","../src/utils/browser.ts","../src/utils/context.ts","../src/utils/filesystem.ts","../src/utils/plugin-storage.ts","../src/utils/http.ts","../src/utils/binary.ts","../src/utils/platform.ts","../src/utils/archive.ts","../src/utils/binary-resolver.ts","../src/utils/cookies.ts","../src/utils/window.ts","../src/utils/events.ts"],"sourcesContent":["/**\n * Tauri core utilities for plugin communication\n */\n\nexport interface TauriCore {\n invoke: <T>(cmd: string, args?: Record<string, unknown>) => Promise<T>;\n}\n\ninterface TauriWindow {\n __TAURI__?: {\n core: TauriCore;\n };\n}\n\n/**\n * Get the Tauri core API\n *\n * @deprecated Use higher-level APIs instead:\n * - File operations: `readFile`, `writeFile`, `listFiles`, `fileExists`\n * - HTTP: `fetchUrl`, `downloadAsset`\n * - Binary execution: `executeBinary`\n * - Cookies: `getPluginCookie`, `setPluginCookie`\n *\n * @throws Error if Tauri is not available\n */\nexport function getTauriCore(): TauriCore {\n const w = window as unknown as TauriWindow;\n if (!w.__TAURI__?.core) {\n throw new Error(\"Tauri core not available\");\n }\n return w.__TAURI__.core;\n}\n\n/**\n * Check if Tauri is available\n */\nexport function isTauriAvailable(): boolean {\n const w = window as unknown as TauriWindow;\n return !!w.__TAURI__?.core;\n}\n","/**\n * Plugin messaging utilities for communicating with Moss\n */\n\nimport type { PluginMessage } from \"../types/messages\";\nimport { getTauriCore, isTauriAvailable } from \"./tauri\";\n\nlet currentPluginName = \"\";\nlet currentHookName = \"\";\n\n/**\n * Set the message context for subsequent messages\n * This is typically called automatically by the plugin runtime\n */\nexport function setMessageContext(pluginName: string, hookName: string): void {\n currentPluginName = pluginName;\n currentHookName = hookName;\n}\n\n/**\n * Get the current message context\n */\nexport function getMessageContext(): { pluginName: string; hookName: string } {\n return { pluginName: currentPluginName, hookName: currentHookName };\n}\n\n/**\n * Send a message to Moss\n * Silently fails if Tauri is unavailable (useful for testing)\n */\nexport async function sendMessage(message: PluginMessage): Promise<void> {\n if (!isTauriAvailable()) {\n return;\n }\n\n try {\n await getTauriCore().invoke(\"plugin_message\", {\n pluginName: currentPluginName,\n hookName: currentHookName,\n message,\n });\n } catch {\n // Silently fail - logging would be recursive\n }\n}\n\n/**\n * Report progress to Moss\n */\nexport async function reportProgress(\n phase: string,\n current: number,\n total: number,\n message?: string\n): Promise<void> {\n await sendMessage({ type: \"progress\", phase, current, total, message });\n}\n\n/**\n * Report an error to Moss\n */\nexport async function reportError(\n error: string,\n context?: string,\n fatal = false\n): Promise<void> {\n await sendMessage({ type: \"error\", error, context, fatal });\n}\n\n/**\n * Report completion to Moss\n */\nexport async function reportComplete(result: unknown): Promise<void> {\n await sendMessage({ type: \"complete\", result });\n}\n","/**\n * Logging utilities for plugins\n */\n\nimport { sendMessage } from \"./messaging\";\n\n/**\n * Log an informational message\n */\nexport async function log(message: string): Promise<void> {\n await sendMessage({ type: \"log\", level: \"log\", message });\n}\n\n/**\n * Log a warning message\n */\nexport async function warn(message: string): Promise<void> {\n await sendMessage({ type: \"log\", level: \"warn\", message });\n}\n\n/**\n * Log an error message\n */\nexport async function error(message: string): Promise<void> {\n await sendMessage({ type: \"log\", level: \"error\", message });\n}\n","/**\n * Browser utilities for plugins\n * Abstracts Tauri browser commands to decouple plugins from internal APIs\n */\n\nimport { getTauriCore } from \"./tauri\";\n\n/**\n * Open a URL in the plugin browser window\n */\nexport async function openBrowser(url: string): Promise<void> {\n await getTauriCore().invoke(\"open_plugin_browser\", { url });\n}\n\n/**\n * Close the plugin browser window\n */\nexport async function closeBrowser(): Promise<void> {\n await getTauriCore().invoke(\"close_plugin_browser\", {});\n}\n","/**\n * Internal context utilities for Moss plugins\n *\n * This module provides access to the plugin execution context that is\n * set by the plugin runtime before each hook execution.\n *\n * INTERNAL USE ONLY - not exported to plugins.\n * Plugins should use the higher-level APIs (readFile, writeFile, etc.)\n * which use this context internally.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Internal plugin execution context\n *\n * Set by the plugin runtime (window.__MOSS_INTERNAL_CONTEXT__)\n * before each hook execution. Cleared after hook completes.\n */\nexport interface InternalPluginContext {\n /** Plugin name from manifest */\n plugin_name: string;\n /** Absolute path to the project directory */\n project_path: string;\n /** Absolute path to the .moss directory */\n moss_dir: string;\n}\n\n/**\n * Window interface with internal context\n */\ninterface ContextWindow {\n __MOSS_INTERNAL_CONTEXT__?: InternalPluginContext;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get the internal plugin context\n *\n * This is used internally by moss-api utilities to resolve paths\n * and plugin identity. Plugins should not call this directly.\n *\n * @returns The current plugin execution context\n * @throws Error if called outside of a plugin hook execution\n *\n * @internal\n */\nexport function getInternalContext(): InternalPluginContext {\n const context = (window as unknown as ContextWindow).__MOSS_INTERNAL_CONTEXT__;\n\n if (!context) {\n throw new Error(\n \"This function must be called from within a plugin hook. \" +\n \"Ensure you're calling this from process(), generate(), deploy(), or syndicate().\"\n );\n }\n\n return context;\n}\n\n/**\n * Check if we're currently inside a plugin hook execution\n *\n * @returns true if inside a hook, false otherwise\n *\n * @internal\n */\nexport function hasContext(): boolean {\n const context = (window as unknown as ContextWindow).__MOSS_INTERNAL_CONTEXT__;\n return context !== undefined;\n}\n","/**\n * File system operations for Moss plugins\n *\n * These functions provide access to project files (user content).\n * Project path is auto-detected from the runtime context.\n *\n * For plugin's private storage, use the plugin-storage API instead.\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Read a file from the project directory\n *\n * Project path is auto-detected from the runtime context.\n *\n * @param relativePath - Path relative to the project root\n * @returns File contents as a string\n * @throws Error if file cannot be read or called outside a hook\n *\n * @example\n * ```typescript\n * // Read an article\n * const content = await readFile(\"article/hello-world.md\");\n *\n * // Read package.json\n * const pkg = JSON.parse(await readFile(\"package.json\"));\n * ```\n */\nexport async function readFile(relativePath: string): Promise<string> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string>(\"read_project_file\", {\n projectPath: ctx.project_path,\n relativePath,\n });\n}\n\n/**\n * Write content to a file in the project directory\n *\n * Creates parent directories if they don't exist.\n * Project path is auto-detected from the runtime context.\n *\n * @param relativePath - Path relative to the project root\n * @param content - Content to write to the file\n * @throws Error if file cannot be written or called outside a hook\n *\n * @example\n * ```typescript\n * // Write a generated article\n * await writeFile(\"article/new-post.md\", \"# Hello World\\n\\nContent here.\");\n *\n * // Write index page\n * await writeFile(\"index.md\", markdownContent);\n * ```\n */\nexport async function writeFile(\n relativePath: string,\n content: string\n): Promise<void> {\n const ctx = getInternalContext();\n\n await getTauriCore().invoke(\"write_project_file\", {\n projectPath: ctx.project_path,\n relativePath,\n data: content,\n });\n}\n\n/**\n * List all files in the project directory\n *\n * Returns file paths relative to the project root.\n * Project path is auto-detected from the runtime context.\n *\n * @returns Array of relative file paths\n * @throws Error if directory cannot be listed or called outside a hook\n *\n * @example\n * ```typescript\n * const files = await listFiles();\n * // [\"index.md\", \"article/hello.md\", \"assets/logo.png\"]\n *\n * const mdFiles = files.filter(f => f.endsWith(\".md\"));\n * ```\n */\nexport async function listFiles(): Promise<string[]> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string[]>(\"list_project_files\", {\n projectPath: ctx.project_path,\n });\n}\n\n/**\n * Check if a file exists in the project directory\n *\n * Project path is auto-detected from the runtime context.\n *\n * @param relativePath - Path relative to the project root\n * @returns true if file exists, false otherwise\n * @throws Error if called outside a hook\n *\n * @example\n * ```typescript\n * if (await fileExists(\"index.md\")) {\n * const content = await readFile(\"index.md\");\n * }\n * ```\n */\nexport async function fileExists(relativePath: string): Promise<boolean> {\n // First, verify we have context (this will throw if not in a hook)\n getInternalContext();\n\n try {\n await readFile(relativePath);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Plugin storage API for Moss plugins\n *\n * Provides access to a plugin's private storage directory at:\n * .moss/plugins/{plugin-name}/\n *\n * Plugin identity is auto-detected from the runtime context -\n * plugins never need to know their own name or path.\n *\n * Config is just a file: readPluginFile(\"config.json\")\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Read a file from the plugin's private storage directory\n *\n * Storage path: .moss/plugins/{plugin-name}/{relativePath}\n *\n * @param relativePath - Path relative to the plugin's storage directory\n * @returns File contents as a string\n * @throws Error if file cannot be read or called outside a hook\n *\n * @example\n * ```typescript\n * // Read plugin config\n * const configJson = await readPluginFile(\"config.json\");\n * const config = JSON.parse(configJson);\n *\n * // Read cached data\n * const cached = await readPluginFile(\"cache/articles.json\");\n * ```\n */\nexport async function readPluginFile(relativePath: string): Promise<string> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string>(\"read_plugin_file\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n relativePath,\n });\n}\n\n/**\n * Write a file to the plugin's private storage directory\n *\n * Creates parent directories if they don't exist.\n * Storage path: .moss/plugins/{plugin-name}/{relativePath}\n *\n * @param relativePath - Path relative to the plugin's storage directory\n * @param content - Content to write to the file\n * @throws Error if file cannot be written or called outside a hook\n *\n * @example\n * ```typescript\n * // Save plugin config\n * await writePluginFile(\"config.json\", JSON.stringify(config, null, 2));\n *\n * // Cache data\n * await writePluginFile(\"cache/articles.json\", JSON.stringify(articles));\n * ```\n */\nexport async function writePluginFile(\n relativePath: string,\n content: string\n): Promise<void> {\n const ctx = getInternalContext();\n\n await getTauriCore().invoke(\"write_plugin_file\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n relativePath,\n content,\n });\n}\n\n/**\n * List all files in the plugin's private storage directory\n *\n * Returns file paths relative to the plugin's storage directory.\n *\n * @returns Array of relative file paths\n * @throws Error if directory cannot be listed or called outside a hook\n *\n * @example\n * ```typescript\n * const files = await listPluginFiles();\n * // [\"config.json\", \"cache/articles.json\", \"cache/images.json\"]\n * ```\n */\nexport async function listPluginFiles(): Promise<string[]> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string[]>(\"list_plugin_files\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n });\n}\n\n/**\n * Check if a file exists in the plugin's private storage directory\n *\n * @param relativePath - Path relative to the plugin's storage directory\n * @returns true if file exists, false otherwise\n * @throws Error if called outside a hook\n *\n * @example\n * ```typescript\n * if (await pluginFileExists(\"config.json\")) {\n * const config = JSON.parse(await readPluginFile(\"config.json\"));\n * } else {\n * // Use default config\n * }\n * ```\n */\nexport async function pluginFileExists(relativePath: string): Promise<boolean> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<boolean>(\"plugin_file_exists\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n relativePath,\n });\n}\n","/**\n * HTTP operations for Moss plugins\n *\n * These functions provide HTTP capabilities that bypass browser CORS\n * restrictions by using Rust's HTTP client under the hood.\n *\n * Project path for downloads is auto-detected from the runtime context.\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for HTTP fetch requests\n */\nexport interface FetchOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n}\n\n/**\n * Result from an HTTP fetch operation\n */\nexport interface FetchResult {\n /** HTTP status code */\n status: number;\n /** Whether the request was successful (2xx status) */\n ok: boolean;\n /** Content-Type header from response */\n contentType: string | null;\n /** Response body as Uint8Array */\n body: Uint8Array;\n /** Get response body as text */\n text(): string;\n}\n\n/**\n * Options for asset download\n */\nexport interface DownloadOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n}\n\n/**\n * Result from an asset download operation\n */\nexport interface DownloadResult {\n /** HTTP status code */\n status: number;\n /** Whether the request was successful (2xx status) */\n ok: boolean;\n /** Content-Type header from response */\n contentType: string | null;\n /** Number of bytes written to disk */\n bytesWritten: number;\n /** Actual path where file was saved (relative to project) */\n actualPath: string;\n}\n\n// ============================================================================\n// Internal Types (Tauri response shapes)\n// ============================================================================\n\ninterface TauriFetchResult {\n status: number;\n ok: boolean;\n body_base64: string;\n content_type: string | null;\n}\n\ninterface TauriDownloadResult {\n status: number;\n ok: boolean;\n content_type: string | null;\n bytes_written: number;\n actual_path: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Fetch a URL using Rust's HTTP client (bypasses CORS)\n *\n * @param url - URL to fetch\n * @param options - Optional fetch configuration\n * @returns Fetch result with status, body, and helpers\n * @throws Error if network request fails\n *\n * @example\n * ```typescript\n * const result = await fetchUrl(\"https://api.example.com/data\");\n * if (result.ok) {\n * const data = JSON.parse(result.text());\n * }\n * ```\n */\nexport async function fetchUrl(\n url: string,\n options: FetchOptions = {}\n): Promise<FetchResult> {\n const { timeoutMs = 30000 } = options;\n\n const result = await getTauriCore().invoke<TauriFetchResult>(\"fetch_url\", {\n url,\n timeoutMs,\n });\n\n // Decode base64 body to Uint8Array\n const binaryString = atob(result.body_base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n return {\n status: result.status,\n ok: result.ok,\n contentType: result.content_type,\n body: bytes,\n text(): string {\n return new TextDecoder().decode(bytes);\n },\n };\n}\n\n/**\n * Download a URL and save directly to disk\n *\n * Downloads the file and writes it directly to disk without passing\n * the binary data through JavaScript. The filename is derived from\n * the URL, and file extension is inferred from Content-Type if needed.\n *\n * Project path is auto-detected from the runtime context.\n *\n * @param url - URL to download\n * @param targetDir - Target directory within project (e.g., \"assets\")\n * @param options - Optional download configuration\n * @returns Download result with actual path where file was saved\n * @throws Error if download or write fails, or called outside a hook\n *\n * @example\n * ```typescript\n * const result = await downloadAsset(\n * \"https://example.com/image\",\n * \"assets\"\n * );\n * if (result.ok) {\n * console.log(`Saved to ${result.actualPath}`); // e.g., \"assets/image.png\"\n * }\n * ```\n */\nexport async function downloadAsset(\n url: string,\n targetDir: string,\n options: DownloadOptions = {}\n): Promise<DownloadResult> {\n const ctx = getInternalContext();\n const { timeoutMs = 30000 } = options;\n\n const result = await getTauriCore().invoke<TauriDownloadResult>(\n \"download_asset\",\n {\n url,\n projectPath: ctx.project_path,\n targetDir,\n timeoutMs,\n }\n );\n\n return {\n status: result.status,\n ok: result.ok,\n contentType: result.content_type,\n bytesWritten: result.bytes_written,\n actualPath: result.actual_path,\n };\n}\n","/**\n * Binary execution for Moss plugins\n *\n * Allows plugins to execute external binaries (git, npm, etc.)\n * in a controlled environment.\n *\n * Working directory is auto-detected from the runtime context\n * (always the project root).\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for executing a binary\n */\nexport interface ExecuteOptions {\n /** Path to the binary (can be just the name if in PATH) */\n binaryPath: string;\n /** Arguments to pass to the binary */\n args: string[];\n /** Timeout in milliseconds (default: 60000) */\n timeoutMs?: number;\n /** Additional environment variables */\n env?: Record<string, string>;\n}\n\n/**\n * Result from binary execution\n */\nexport interface ExecuteResult {\n /** Whether the command succeeded (exit code 0) */\n success: boolean;\n /** Exit code from the process */\n exitCode: number;\n /** Standard output from the process */\n stdout: string;\n /** Standard error output from the process */\n stderr: string;\n}\n\n// ============================================================================\n// Internal Types (Tauri response shape)\n// ============================================================================\n\ninterface TauriBinaryResult {\n success: boolean;\n exit_code: number;\n stdout: string;\n stderr: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Execute an external binary\n *\n * Working directory is auto-detected from the runtime context\n * (always the project root).\n *\n * @param options - Execution options including binary path and args\n * @returns Execution result with stdout, stderr, and exit code\n * @throws Error if binary cannot be executed or called outside a hook\n *\n * @example\n * ```typescript\n * // Run git status\n * const result = await executeBinary({\n * binaryPath: \"git\",\n * args: [\"status\"],\n * });\n *\n * if (result.success) {\n * console.log(result.stdout);\n * } else {\n * console.error(result.stderr);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Run npm install with timeout\n * const result = await executeBinary({\n * binaryPath: \"npm\",\n * args: [\"install\"],\n * timeoutMs: 120000,\n * env: { NODE_ENV: \"production\" },\n * });\n * ```\n */\nexport async function executeBinary(\n options: ExecuteOptions\n): Promise<ExecuteResult> {\n const ctx = getInternalContext();\n const { binaryPath, args, timeoutMs = 60000, env } = options;\n\n const result = await getTauriCore().invoke<TauriBinaryResult>(\n \"execute_binary\",\n {\n binaryPath,\n args,\n workingDir: ctx.project_path,\n timeoutMs,\n env,\n }\n );\n\n return {\n success: result.success,\n exitCode: result.exit_code,\n stdout: result.stdout,\n stderr: result.stderr,\n };\n}\n","/**\n * Platform detection utilities for Moss plugins\n *\n * Detects the current operating system and architecture to enable\n * platform-specific binary downloads and operations.\n */\n\nimport { executeBinary } from \"./binary\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Supported operating systems\n */\nexport type OSType = \"darwin\" | \"linux\" | \"windows\";\n\n/**\n * Supported architectures\n */\nexport type ArchType = \"arm64\" | \"x64\";\n\n/**\n * Platform key combining OS and architecture\n */\nexport type PlatformKey =\n | \"darwin-arm64\"\n | \"darwin-x64\"\n | \"linux-x64\"\n | \"windows-x64\";\n\n/**\n * Complete platform information\n */\nexport interface PlatformInfo {\n /** Operating system */\n os: OSType;\n /** CPU architecture */\n arch: ArchType;\n /** Combined platform key for binary selection */\n platformKey: PlatformKey;\n}\n\n// ============================================================================\n// Cached Detection\n// ============================================================================\n\nlet cachedPlatform: PlatformInfo | null = null;\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Detect the current platform (OS and architecture)\n *\n * Uses system commands to detect the platform:\n * - On macOS/Linux: `uname -s` for OS, `uname -m` for architecture\n * - On Windows: Falls back to environment variables and defaults\n *\n * Results are cached after the first call.\n *\n * @returns Platform information including OS, architecture, and combined key\n * @throws Error if platform detection fails or platform is unsupported\n *\n * @example\n * ```typescript\n * const platform = await getPlatformInfo();\n * console.log(platform.platformKey); // \"darwin-arm64\"\n * ```\n */\nexport async function getPlatformInfo(): Promise<PlatformInfo> {\n // Return cached result if available\n if (cachedPlatform) {\n return cachedPlatform;\n }\n\n const os = await detectOS();\n const arch = await detectArch(os);\n\n // Validate supported platform combinations\n const platformKey = `${os}-${arch}` as PlatformKey;\n const supportedPlatforms: PlatformKey[] = [\n \"darwin-arm64\",\n \"darwin-x64\",\n \"linux-x64\",\n \"windows-x64\",\n ];\n\n if (!supportedPlatforms.includes(platformKey)) {\n throw new Error(\n `Unsupported platform: ${platformKey}. ` +\n `Supported platforms: ${supportedPlatforms.join(\", \")}`\n );\n }\n\n cachedPlatform = { os, arch, platformKey };\n return cachedPlatform;\n}\n\n/**\n * Clear the cached platform info\n *\n * Useful for testing or when platform detection needs to be re-run.\n *\n * @internal\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n}\n\n// ============================================================================\n// Internal Detection Functions\n// ============================================================================\n\n/**\n * Detect the operating system\n */\nasync function detectOS(): Promise<OSType> {\n try {\n // Try uname first (works on macOS and Linux)\n const result = await executeBinary({\n binaryPath: \"uname\",\n args: [\"-s\"],\n timeoutMs: 5000,\n });\n\n if (result.success) {\n const osName = result.stdout.trim().toLowerCase();\n if (osName === \"darwin\") {\n return \"darwin\";\n }\n if (osName === \"linux\") {\n return \"linux\";\n }\n }\n } catch {\n // uname not available, likely Windows\n }\n\n // Check for Windows via cmd\n try {\n const result = await executeBinary({\n binaryPath: \"cmd\",\n args: [\"/c\", \"ver\"],\n timeoutMs: 5000,\n });\n\n if (result.success && result.stdout.toLowerCase().includes(\"windows\")) {\n return \"windows\";\n }\n } catch {\n // Not Windows either\n }\n\n throw new Error(\n \"Unable to detect operating system. \" +\n \"Supported systems: macOS (Darwin), Linux, Windows\"\n );\n}\n\n/**\n * Detect the CPU architecture\n */\nasync function detectArch(os: OSType): Promise<ArchType> {\n if (os === \"windows\") {\n // On Windows, check PROCESSOR_ARCHITECTURE environment variable\n try {\n const result = await executeBinary({\n binaryPath: \"cmd\",\n args: [\"/c\", \"echo\", \"%PROCESSOR_ARCHITECTURE%\"],\n timeoutMs: 5000,\n });\n\n if (result.success) {\n const arch = result.stdout.trim().toLowerCase();\n if (arch === \"arm64\") {\n return \"arm64\";\n }\n // AMD64, x86_64, etc. -> x64\n return \"x64\";\n }\n } catch {\n // Default to x64 on Windows\n return \"x64\";\n }\n }\n\n // Unix-like systems (macOS, Linux)\n try {\n const result = await executeBinary({\n binaryPath: \"uname\",\n args: [\"-m\"],\n timeoutMs: 5000,\n });\n\n if (result.success) {\n const machine = result.stdout.trim().toLowerCase();\n if (machine === \"arm64\" || machine === \"aarch64\") {\n return \"arm64\";\n }\n if (machine === \"x86_64\" || machine === \"amd64\") {\n return \"x64\";\n }\n // Fallback for other architectures\n if (machine.includes(\"arm\")) {\n return \"arm64\";\n }\n return \"x64\";\n }\n } catch {\n // Fallback\n }\n\n // Default to x64 if detection fails\n return \"x64\";\n}\n","/**\n * Archive extraction utilities for Moss plugins\n *\n * Provides functions to extract .tar.gz and .zip archives using\n * system commands (tar, unzip, PowerShell).\n */\n\nimport { executeBinary } from \"./binary\";\nimport { getPlatformInfo } from \"./platform\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Supported archive formats\n */\nexport type ArchiveFormat = \"tar.gz\" | \"zip\";\n\n/**\n * Options for archive extraction\n */\nexport interface ExtractOptions {\n /** Path to the archive file (absolute) */\n archivePath: string;\n /** Directory to extract to (absolute) */\n destDir: string;\n /** Archive format (auto-detected from extension if not provided) */\n format?: ArchiveFormat;\n /** Timeout in milliseconds (default: 60000) */\n timeoutMs?: number;\n}\n\n/**\n * Result of archive extraction\n */\nexport interface ExtractResult {\n /** Whether extraction succeeded */\n success: boolean;\n /** Error message if extraction failed */\n error?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Extract an archive to a destination directory\n *\n * Uses system commands for extraction:\n * - .tar.gz: `tar -xzf` (macOS/Linux)\n * - .zip: `unzip` (macOS/Linux) or PowerShell `Expand-Archive` (Windows)\n *\n * @param options - Extraction options\n * @returns Extraction result\n *\n * @example\n * ```typescript\n * const result = await extractArchive({\n * archivePath: \"/path/to/hugo.tar.gz\",\n * destDir: \"/path/to/extract/\",\n * });\n *\n * if (!result.success) {\n * console.error(`Extraction failed: ${result.error}`);\n * }\n * ```\n */\nexport async function extractArchive(\n options: ExtractOptions\n): Promise<ExtractResult> {\n const { archivePath, destDir, timeoutMs = 60000 } = options;\n\n // Auto-detect format from extension if not provided\n const format = options.format ?? detectFormat(archivePath);\n\n if (!format) {\n return {\n success: false,\n error: `Unable to detect archive format for: ${archivePath}. Supported formats: .tar.gz, .zip`,\n };\n }\n\n const platform = await getPlatformInfo();\n\n try {\n if (format === \"tar.gz\") {\n return await extractTarGz(archivePath, destDir, timeoutMs);\n } else {\n // zip format\n if (platform.os === \"windows\") {\n return await extractZipWindows(archivePath, destDir, timeoutMs);\n } else {\n return await extractZipUnix(archivePath, destDir, timeoutMs);\n }\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Make a file executable (Unix only)\n *\n * Runs `chmod +x` on the specified file. No-op on Windows.\n *\n * @param filePath - Absolute path to the file\n * @returns Whether the operation succeeded\n *\n * @example\n * ```typescript\n * await makeExecutable(\"/path/to/binary\");\n * ```\n */\nexport async function makeExecutable(filePath: string): Promise<boolean> {\n const platform = await getPlatformInfo();\n\n // No-op on Windows - executables don't need chmod\n if (platform.os === \"windows\") {\n return true;\n }\n\n try {\n const result = await executeBinary({\n binaryPath: \"chmod\",\n args: [\"+x\", filePath],\n timeoutMs: 5000,\n });\n\n return result.success;\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Internal Functions\n// ============================================================================\n\n/**\n * Detect archive format from file extension\n */\nfunction detectFormat(archivePath: string): ArchiveFormat | null {\n const lowerPath = archivePath.toLowerCase();\n\n if (lowerPath.endsWith(\".tar.gz\") || lowerPath.endsWith(\".tgz\")) {\n return \"tar.gz\";\n }\n if (lowerPath.endsWith(\".zip\")) {\n return \"zip\";\n }\n\n return null;\n}\n\n/**\n * Extract .tar.gz using tar command\n */\nasync function extractTarGz(\n archivePath: string,\n destDir: string,\n timeoutMs: number\n): Promise<ExtractResult> {\n const result = await executeBinary({\n binaryPath: \"tar\",\n args: [\"-xzf\", archivePath, \"-C\", destDir],\n timeoutMs,\n });\n\n if (!result.success) {\n return {\n success: false,\n error: result.stderr || `tar extraction failed with exit code ${result.exitCode}`,\n };\n }\n\n return { success: true };\n}\n\n/**\n * Extract .zip using unzip command (macOS/Linux)\n */\nasync function extractZipUnix(\n archivePath: string,\n destDir: string,\n timeoutMs: number\n): Promise<ExtractResult> {\n const result = await executeBinary({\n binaryPath: \"unzip\",\n args: [\"-o\", \"-q\", archivePath, \"-d\", destDir],\n timeoutMs,\n });\n\n if (!result.success) {\n return {\n success: false,\n error: result.stderr || `unzip extraction failed with exit code ${result.exitCode}`,\n };\n }\n\n return { success: true };\n}\n\n/**\n * Extract .zip using PowerShell (Windows)\n */\nasync function extractZipWindows(\n archivePath: string,\n destDir: string,\n timeoutMs: number\n): Promise<ExtractResult> {\n // Use PowerShell's Expand-Archive cmdlet\n const command = `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`;\n\n const result = await executeBinary({\n binaryPath: \"powershell\",\n args: [\"-NoProfile\", \"-NonInteractive\", \"-Command\", command],\n timeoutMs,\n });\n\n if (!result.success) {\n return {\n success: false,\n error: result.stderr || `PowerShell extraction failed with exit code ${result.exitCode}`,\n };\n }\n\n return { success: true };\n}\n","/**\n * Binary resolver for Moss plugins\n *\n * Provides auto-detection and download of external CLI tools like Hugo.\n * Implements a 4-step resolution flow:\n * 1. Check configured path (from plugin config)\n * 2. Check system PATH\n * 3. Check plugin bin directory\n * 4. Download from GitHub releases (if enabled)\n */\n\nimport { executeBinary } from \"./binary\";\nimport { fetchUrl } from \"./http\";\nimport { pluginFileExists, writePluginFile, readPluginFile } from \"./plugin-storage\";\nimport { getInternalContext } from \"./context\";\nimport { getPlatformInfo, type PlatformKey } from \"./platform\";\nimport { extractArchive, makeExecutable } from \"./archive\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * GitHub source configuration for binary downloads\n */\nexport interface GitHubSource {\n /** Repository owner (e.g., \"gohugoio\") */\n owner: string;\n /** Repository name (e.g., \"hugo\") */\n repo: string;\n /**\n * Asset filename pattern with placeholders:\n * - {version}: Version number (e.g., \"0.139.0\")\n * - {os}: Operating system (darwin, linux, windows)\n * - {arch}: Architecture (arm64, amd64, x64)\n *\n * Example: \"hugo_extended_{version}_{os}-{arch}.tar.gz\"\n */\n assetPattern: string;\n}\n\n/**\n * Binary source configuration per platform\n */\nexport interface BinarySource {\n /** GitHub release source */\n github?: GitHubSource;\n /** Direct download URL (with same placeholders as assetPattern) */\n directUrl?: string;\n}\n\n/**\n * Configuration for a binary to resolve\n */\nexport interface BinaryConfig {\n /** Binary name (e.g., \"hugo\") */\n name: string;\n /** Minimum version required (semver, optional) */\n minVersion?: string;\n /** Command to check version (default: \"{name} version\") */\n versionCommand?: string;\n /** Regex to extract version from command output */\n versionPattern?: RegExp;\n /** Download sources per platform */\n sources: Partial<Record<PlatformKey, BinarySource>>;\n /** Binary filename inside archive (default: same as name, or name.exe on Windows) */\n binaryName?: string;\n}\n\n/**\n * Result of binary resolution\n */\nexport interface BinaryResolution {\n /** Absolute path to the binary */\n path: string;\n /** Detected version (if available) */\n version?: string;\n /** How the binary was found */\n source: \"config\" | \"path\" | \"plugin-storage\" | \"downloaded\";\n}\n\n/**\n * Options for binary resolution\n */\nexport interface ResolveBinaryOptions {\n /** Plugin's configured binary path (from context.config) */\n configuredPath?: string;\n /** Whether to auto-download if not found (default: true) */\n autoDownload?: boolean;\n /** Progress callback for UI feedback */\n onProgress?: (phase: string, message: string) => void;\n}\n\n/**\n * Error thrown during binary resolution\n */\nexport class BinaryResolutionError extends Error {\n constructor(\n message: string,\n public readonly phase: \"detection\" | \"download\" | \"extraction\" | \"validation\",\n public readonly cause?: Error\n ) {\n super(message);\n this.name = \"BinaryResolutionError\";\n }\n}\n\n// ============================================================================\n// Main Resolution Function\n// ============================================================================\n\n/**\n * Resolve a binary, downloading if necessary\n *\n * Resolution order:\n * 1. Configured path (from plugin config, e.g., hugo_path)\n * 2. System PATH (just the binary name)\n * 3. Plugin bin directory (.moss/plugins/{plugin}/bin/{name})\n * 4. Download from GitHub releases (if autoDownload is true)\n *\n * @param config - Binary configuration\n * @param options - Resolution options\n * @returns Resolution result with path and source\n * @throws BinaryResolutionError if binary cannot be resolved\n *\n * @example\n * ```typescript\n * const hugo = await resolveBinary(HUGO_CONFIG, {\n * configuredPath: context.config.hugo_path,\n * onProgress: (phase, msg) => reportProgress(phase, 0, 1, msg),\n * });\n *\n * await executeBinary({\n * binaryPath: hugo.path,\n * args: [\"--version\"],\n * });\n * ```\n */\nexport async function resolveBinary(\n config: BinaryConfig,\n options: ResolveBinaryOptions = {}\n): Promise<BinaryResolution> {\n const { configuredPath, autoDownload = true, onProgress } = options;\n\n const progress = (phase: string, message: string) => {\n onProgress?.(phase, message);\n };\n\n // Step 1: Check configured path\n if (configuredPath) {\n progress(\"detection\", `Checking configured path: ${configuredPath}`);\n const result = await checkBinary(configuredPath, config);\n if (result) {\n return { path: configuredPath, version: result.version, source: \"config\" };\n }\n }\n\n // Step 2: Check system PATH\n progress(\"detection\", `Checking system PATH for ${config.name}`);\n const pathResult = await checkBinary(config.name, config);\n if (pathResult) {\n return { path: config.name, version: pathResult.version, source: \"path\" };\n }\n\n // Step 3: Check plugin bin directory\n const pluginBinPath = await getPluginBinPath(config);\n progress(\"detection\", `Checking plugin storage: ${pluginBinPath}`);\n\n if (await binaryExistsInPluginStorage(config)) {\n const storedResult = await checkBinary(pluginBinPath, config);\n if (storedResult) {\n return { path: pluginBinPath, version: storedResult.version, source: \"plugin-storage\" };\n }\n }\n\n // Step 4: Download if enabled\n if (!autoDownload) {\n throw new BinaryResolutionError(\n `${config.name} not found. ` +\n `Please install it manually or set the path in plugin configuration.\\n\\n` +\n `Installation options:\\n` +\n `- Install via package manager (brew, apt, etc.)\\n` +\n `- Download from the official website\\n` +\n `- Set ${config.name}_path in .moss/config.toml`,\n \"detection\"\n );\n }\n\n progress(\"download\", `Downloading ${config.name}...`);\n const downloadedPath = await downloadBinary(config, progress);\n\n // Verify the downloaded binary works\n const downloadedResult = await checkBinary(downloadedPath, config);\n if (!downloadedResult) {\n throw new BinaryResolutionError(\n `Downloaded ${config.name} binary failed verification. ` +\n `The binary may be corrupted or incompatible with your system.`,\n \"validation\"\n );\n }\n\n return {\n path: downloadedPath,\n version: downloadedResult.version,\n source: \"downloaded\",\n };\n}\n\n// ============================================================================\n// Binary Checking\n// ============================================================================\n\ninterface CheckResult {\n version?: string;\n}\n\n/**\n * Check if a binary exists and optionally extract its version\n */\nasync function checkBinary(\n binaryPath: string,\n config: BinaryConfig\n): Promise<CheckResult | null> {\n try {\n // Build version command\n const versionCmd = config.versionCommand ?? `${config.name} version`;\n const [cmd, ...args] = parseCommand(versionCmd, binaryPath, config.name);\n\n const result = await executeBinary({\n binaryPath: cmd,\n args,\n timeoutMs: 10000,\n });\n\n if (!result.success) {\n return null;\n }\n\n // Extract version if pattern provided\n let version: string | undefined;\n if (config.versionPattern) {\n const output = result.stdout + result.stderr;\n const match = output.match(config.versionPattern);\n if (match && match[1]) {\n version = match[1];\n }\n }\n\n return { version };\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a version command, replacing {name} with the binary path\n */\nfunction parseCommand(\n template: string,\n binaryPath: string,\n name: string\n): string[] {\n // Replace {name} with actual binary path\n const resolved = template.replace(/{name}/g, binaryPath);\n\n // If the template is just \"{name} version\", use the binaryPath directly\n if (resolved.startsWith(binaryPath)) {\n const rest = resolved.slice(binaryPath.length).trim();\n return [binaryPath, ...rest.split(/\\s+/).filter(Boolean)];\n }\n\n // Otherwise split normally\n return resolved.split(/\\s+/).filter(Boolean);\n}\n\n// ============================================================================\n// Plugin Storage Helpers\n// ============================================================================\n\n/**\n * Get the full path to the binary in plugin storage\n */\nasync function getPluginBinPath(config: BinaryConfig): Promise<string> {\n const ctx = getInternalContext();\n const platform = await getPlatformInfo();\n\n const binaryName = getBinaryFilename(config, platform.os === \"windows\");\n return `${ctx.moss_dir}/plugins/${ctx.plugin_name}/bin/${binaryName}`;\n}\n\n/**\n * Get the binary filename (with .exe on Windows)\n */\nfunction getBinaryFilename(config: BinaryConfig, isWindows: boolean): string {\n const baseName = config.binaryName ?? config.name;\n return isWindows ? `${baseName}.exe` : baseName;\n}\n\n/**\n * Check if binary exists in plugin storage\n */\nasync function binaryExistsInPluginStorage(config: BinaryConfig): Promise<boolean> {\n const platform = await getPlatformInfo();\n const binaryName = getBinaryFilename(config, platform.os === \"windows\");\n return pluginFileExists(`bin/${binaryName}`);\n}\n\n// ============================================================================\n// Download Logic\n// ============================================================================\n\n/**\n * Download and extract binary from GitHub releases\n */\nasync function downloadBinary(\n config: BinaryConfig,\n progress: (phase: string, message: string) => void\n): Promise<string> {\n const platform = await getPlatformInfo();\n const source = config.sources[platform.platformKey];\n\n if (!source) {\n throw new BinaryResolutionError(\n `No download source configured for platform: ${platform.platformKey}`,\n \"download\"\n );\n }\n\n // Get latest version from GitHub\n let version: string;\n let downloadUrl: string;\n\n if (source.github) {\n progress(\"download\", \"Fetching latest release info from GitHub...\");\n const releaseInfo = await getLatestRelease(source.github.owner, source.github.repo);\n version = releaseInfo.version;\n\n // Construct download URL\n const assetName = resolveAssetPattern(source.github.assetPattern, version, platform);\n downloadUrl = `https://github.com/${source.github.owner}/${source.github.repo}/releases/download/v${version}/${assetName}`;\n } else if (source.directUrl) {\n // Use direct URL (version needs to be fetched separately or hardcoded)\n throw new BinaryResolutionError(\n \"Direct URL downloads not yet implemented. Please use GitHub source.\",\n \"download\"\n );\n } else {\n throw new BinaryResolutionError(\n `No download source configured for ${config.name}`,\n \"download\"\n );\n }\n\n progress(\"download\", `Downloading ${config.name} v${version}...`);\n\n // Download to temp location\n const ctx = getInternalContext();\n const archiveFilename = downloadUrl.split(\"/\").pop() ?? \"archive\";\n const archivePath = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/.tmp/${archiveFilename}`;\n\n await downloadToPluginStorage(downloadUrl, `.tmp/${archiveFilename}`);\n\n // Extract archive\n progress(\"extraction\", \"Extracting archive...\");\n const binDir = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/bin`;\n\n // Ensure bin directory exists by writing a marker file\n await writePluginFile(\"bin/.gitkeep\", \"\");\n\n const extractResult = await extractArchive({\n archivePath,\n destDir: binDir,\n });\n\n if (!extractResult.success) {\n throw new BinaryResolutionError(\n `Failed to extract archive: ${extractResult.error}`,\n \"extraction\"\n );\n }\n\n // Make binary executable (Unix only)\n const binaryPath = await getPluginBinPath(config);\n await makeExecutable(binaryPath);\n\n // Clean up temp archive\n // Note: We don't have a delete API, so we just leave it for now\n // The temp file will be overwritten on next download\n\n progress(\"complete\", `${config.name} v${version} installed successfully`);\n\n // Cache the version info\n await cacheReleaseInfo(config.name, version);\n\n return binaryPath;\n}\n\n/**\n * Fetch latest release info from GitHub API\n */\nasync function getLatestRelease(\n owner: string,\n repo: string\n): Promise<{ version: string; tag: string }> {\n // Try to use cached info if GitHub API fails\n const cacheKey = `${owner}/${repo}`;\n\n try {\n const response = await fetchUrl(\n `https://api.github.com/repos/${owner}/${repo}/releases/latest`,\n { timeoutMs: 10000 }\n );\n\n if (!response.ok) {\n // Check for rate limiting\n if (response.status === 403 || response.status === 429) {\n const cached = await getCachedRelease(cacheKey);\n if (cached) {\n return cached;\n }\n throw new BinaryResolutionError(\n \"GitHub API rate limit exceeded. Please try again later or install the binary manually.\",\n \"download\"\n );\n }\n throw new BinaryResolutionError(\n `Failed to fetch release info: HTTP ${response.status}`,\n \"download\"\n );\n }\n\n const data = JSON.parse(response.text());\n const tag = data.tag_name as string;\n const version = tag.replace(/^v/, \"\"); // \"v0.139.0\" -> \"0.139.0\"\n\n return { version, tag };\n } catch (error) {\n if (error instanceof BinaryResolutionError) {\n throw error;\n }\n\n // Try cache on any error\n const cached = await getCachedRelease(cacheKey);\n if (cached) {\n return cached;\n }\n\n throw new BinaryResolutionError(\n `Failed to fetch release info: ${error instanceof Error ? error.message : String(error)}`,\n \"download\",\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Resolve asset pattern with actual values\n */\nfunction resolveAssetPattern(\n pattern: string,\n version: string,\n platform: { os: string; arch: string }\n): string {\n // Map our arch names to common GitHub naming conventions\n const archMap: Record<string, string> = {\n x64: \"amd64\",\n arm64: \"arm64\",\n };\n\n return pattern\n .replace(/{version}/g, version)\n .replace(/{os}/g, platform.os)\n .replace(/{arch}/g, archMap[platform.arch] ?? platform.arch);\n}\n\n/**\n * Download a file to plugin storage\n *\n * Uses curl (macOS/Linux) or PowerShell (Windows) to download the file\n * directly to the target path, avoiding memory limitations of base64 encoding.\n */\nasync function downloadToPluginStorage(\n url: string,\n relativePath: string\n): Promise<void> {\n const ctx = getInternalContext();\n const platform = await getPlatformInfo();\n\n const targetPath = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/${relativePath}`;\n\n // Ensure parent directory exists\n const parentDir = targetPath.substring(0, targetPath.lastIndexOf(\"/\"));\n\n if (platform.os === \"windows\") {\n // Use PowerShell for Windows\n const mkdirCmd = `New-Item -ItemType Directory -Force -Path '${parentDir}'`;\n await executeBinary({\n binaryPath: \"powershell\",\n args: [\"-NoProfile\", \"-NonInteractive\", \"-Command\", mkdirCmd],\n timeoutMs: 5000,\n });\n\n const downloadCmd = `Invoke-WebRequest -Uri '${url}' -OutFile '${targetPath}'`;\n const result = await executeBinary({\n binaryPath: \"powershell\",\n args: [\"-NoProfile\", \"-NonInteractive\", \"-Command\", downloadCmd],\n timeoutMs: 300000, // 5 minutes\n });\n\n if (!result.success) {\n throw new BinaryResolutionError(\n `Download failed: ${result.stderr || result.stdout}`,\n \"download\"\n );\n }\n } else {\n // Use curl for macOS/Linux (pre-installed on both)\n await executeBinary({\n binaryPath: \"mkdir\",\n args: [\"-p\", parentDir],\n timeoutMs: 5000,\n });\n\n const result = await executeBinary({\n binaryPath: \"curl\",\n args: [\n \"-fsSL\", // fail silently, follow redirects, show errors\n \"--create-dirs\",\n \"-o\", targetPath,\n url,\n ],\n timeoutMs: 300000, // 5 minutes\n });\n\n if (!result.success) {\n throw new BinaryResolutionError(\n `Download failed: ${result.stderr || `curl exited with code ${result.exitCode}`}`,\n \"download\"\n );\n }\n }\n}\n\n// ============================================================================\n// Caching\n// ============================================================================\n\ninterface CachedRelease {\n version: string;\n tag: string;\n cachedAt: string;\n}\n\n/**\n * Cache release info for fallback\n */\nasync function cacheReleaseInfo(name: string, version: string): Promise<void> {\n try {\n const cached: CachedRelease = {\n version,\n tag: `v${version}`,\n cachedAt: new Date().toISOString(),\n };\n await writePluginFile(\n `cache/${name}-release.json`,\n JSON.stringify(cached, null, 2)\n );\n } catch {\n // Ignore cache write failures\n }\n}\n\n/**\n * Get cached release info\n */\nasync function getCachedRelease(\n cacheKey: string\n): Promise<{ version: string; tag: string } | null> {\n try {\n // Extract name from cache key (owner/repo -> repo)\n const name = cacheKey.split(\"/\")[1];\n if (!name) return null;\n\n if (!(await pluginFileExists(`cache/${name}-release.json`))) {\n return null;\n }\n\n const content = await readPluginFile(`cache/${name}-release.json`);\n const cached = JSON.parse(content) as CachedRelease;\n\n return { version: cached.version, tag: cached.tag };\n } catch {\n return null;\n }\n}\n","/**\n * Cookie management for Moss plugins\n *\n * Allows plugins to store and retrieve authentication cookies\n * for external services (e.g., Matters.town, GitHub).\n *\n * Cookies are automatically scoped to the plugin's registered domain\n * (defined in manifest.json) - plugins cannot access other plugins' cookies.\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A cookie stored for plugin authentication\n */\nexport interface Cookie {\n /** Cookie name */\n name: string;\n /** Cookie value */\n value: string;\n /** Optional domain for the cookie */\n domain?: string;\n /** Optional path for the cookie */\n path?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get stored cookies for the current plugin.\n *\n * The plugin's identity is automatically detected from the runtime context.\n * Cookies are filtered by the domain declared in the plugin's manifest.json.\n *\n * **Must be called from within a plugin hook** (process, generate, deploy, syndicate).\n *\n * @returns Array of cookies for the plugin's registered domain\n * @throws Error if called outside of a plugin hook execution\n *\n * @example\n * ```typescript\n * // Inside a hook function:\n * const cookies = await getPluginCookie();\n * const token = cookies.find(c => c.name === \"__access_token\");\n * if (token) {\n * // Use token for authenticated requests\n * }\n * ```\n */\nexport async function getPluginCookie(): Promise<Cookie[]> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<Cookie[]>(\"get_plugin_cookie\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n });\n}\n\n/**\n * Store cookies for the current plugin.\n *\n * The plugin's identity is automatically detected from the runtime context.\n *\n * **Must be called from within a plugin hook** (process, generate, deploy, syndicate).\n *\n * @param cookies - Array of cookies to store\n * @throws Error if called outside of a plugin hook execution\n *\n * @example\n * ```typescript\n * // Inside a hook function:\n * await setPluginCookie([\n * { name: \"session\", value: \"abc123\" }\n * ]);\n * ```\n */\nexport async function setPluginCookie(cookies: Cookie[]): Promise<void> {\n const ctx = getInternalContext();\n\n await getTauriCore().invoke(\"set_plugin_cookie\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n cookies,\n });\n}\n","/**\n * Window utilities for plugins\n * Enables plugins to show custom dialogs and UI elements\n */\n\nimport { getTauriCore } from \"./tauri\";\n\n/**\n * Result from a dialog interaction\n */\nexport interface DialogResult {\n type: \"submitted\" | \"cancelled\";\n value?: unknown;\n}\n\n/**\n * Options for showing a plugin dialog\n */\nexport interface ShowDialogOptions {\n /** URL to load in the dialog (can be data: URL with embedded HTML) */\n url: string;\n /** Dialog window title */\n title: string;\n /** Dialog width in pixels */\n width?: number;\n /** Dialog height in pixels */\n height?: number;\n /** Maximum time to wait for user response in milliseconds */\n timeoutMs?: number;\n}\n\n/**\n * Show a plugin dialog and wait for user response\n *\n * The dialog can be an embedded HTML page (via data: URL) that communicates\n * back to the plugin via the submitDialogResult function.\n *\n * @param options - Dialog configuration\n * @returns Dialog result with submitted value or cancellation\n *\n * @example\n * ```typescript\n * const result = await showPluginDialog({\n * url: createMyDialogUrl(),\n * title: \"Create Repository\",\n * width: 400,\n * height: 300,\n * });\n *\n * if (result.type === \"submitted\") {\n * console.log(\"User submitted:\", result.value);\n * } else {\n * console.log(\"User cancelled\");\n * }\n * ```\n */\nexport async function showPluginDialog(\n options: ShowDialogOptions\n): Promise<DialogResult> {\n const result = await getTauriCore().invoke<DialogResult>(\n \"show_plugin_dialog\",\n {\n url: options.url,\n title: options.title,\n width: options.width ?? 500,\n height: options.height ?? 400,\n timeoutMs: options.timeoutMs ?? 300000, // 5 minutes default\n }\n );\n return result;\n}\n\n/**\n * Submit a result from within a plugin dialog\n *\n * This is called from inside the dialog HTML to send data back to the plugin.\n * The dialog will be closed automatically after submission.\n *\n * @param dialogId - The dialog ID (provided in the dialog's query string)\n * @param value - The value to submit\n * @returns Whether the submission was successful\n *\n * @example\n * ```typescript\n * // Inside dialog HTML:\n * const dialogId = new URLSearchParams(location.search).get('dialogId');\n * await submitDialogResult(dialogId, { repoName: 'my-repo' });\n * ```\n */\nexport async function submitDialogResult(\n dialogId: string,\n value: unknown\n): Promise<boolean> {\n return getTauriCore().invoke<boolean>(\"submit_dialog_result\", {\n dialogId,\n result: { type: \"submitted\", value },\n });\n}\n\n/**\n * Cancel a plugin dialog\n *\n * This is called from inside the dialog HTML to cancel without submitting.\n * The dialog will be closed automatically.\n *\n * @param dialogId - The dialog ID (provided in the dialog's query string)\n *\n * @example\n * ```typescript\n * // Inside dialog HTML:\n * const dialogId = new URLSearchParams(location.search).get('dialogId');\n * await cancelDialog(dialogId);\n * ```\n */\nexport async function cancelDialog(dialogId: string): Promise<boolean> {\n return getTauriCore().invoke<boolean>(\"submit_dialog_result\", {\n dialogId,\n result: { type: \"cancelled\" },\n });\n}\n","/**\n * Event utilities for plugin communication\n *\n * Provides a way for plugins and dialog windows to communicate\n * via Tauri's event system.\n */\n\n/**\n * Tauri event listener interface\n */\ninterface TauriEventListen {\n (event: string, handler: (event: { payload: unknown }) => void): Promise<() => void>;\n}\n\n/**\n * Tauri event emit interface\n */\ninterface TauriEventEmit {\n (event: string, payload?: unknown): Promise<void>;\n}\n\ninterface TauriEventWindow {\n __TAURI__?: {\n event?: {\n listen: TauriEventListen;\n emit: TauriEventEmit;\n };\n };\n}\n\n/**\n * Get Tauri event API\n * @internal\n */\nfunction getTauriEvent(): { listen: TauriEventListen; emit: TauriEventEmit } {\n const w = window as unknown as TauriEventWindow;\n if (!w.__TAURI__?.event) {\n throw new Error(\"Tauri event API not available\");\n }\n return w.__TAURI__.event;\n}\n\n/**\n * Check if Tauri event API is available\n */\nexport function isEventApiAvailable(): boolean {\n const w = window as unknown as TauriEventWindow;\n return !!w.__TAURI__?.event;\n}\n\n/**\n * Emit an event to other parts of the application\n *\n * @param event - Event name (e.g., \"repo-created\", \"dialog-result\")\n * @param payload - Data to send with the event\n *\n * @example\n * ```typescript\n * // From dialog:\n * await emitEvent(\"repo-name-validated\", { name: \"my-repo\", available: true });\n *\n * // From plugin:\n * await emitEvent(\"deployment-started\", { url: \"https://github.com/...\" });\n * ```\n */\nexport async function emitEvent(event: string, payload?: unknown): Promise<void> {\n await getTauriEvent().emit(event, payload);\n}\n\n/**\n * Listen for events from other parts of the application\n *\n * @param event - Event name to listen for\n * @param handler - Function to call when event is received\n * @returns Cleanup function to stop listening\n *\n * @example\n * ```typescript\n * const unlisten = await onEvent<{ name: string; available: boolean }>(\n * \"repo-name-validated\",\n * (data) => {\n * console.log(`Repo ${data.name} is ${data.available ? \"available\" : \"taken\"}`);\n * }\n * );\n *\n * // Later, to stop listening:\n * unlisten();\n * ```\n */\nexport async function onEvent<T>(\n event: string,\n handler: (payload: T) => void\n): Promise<() => void> {\n const unlisten = await getTauriEvent().listen(event, (e) => {\n handler(e.payload as T);\n });\n return unlisten;\n}\n\n/**\n * Wait for a single event occurrence\n *\n * @param event - Event name to wait for\n * @param timeoutMs - Maximum time to wait (default: 30000ms)\n * @returns Promise that resolves with the event payload\n * @throws Error if timeout is reached\n *\n * @example\n * ```typescript\n * try {\n * const result = await waitForEvent<{ confirmed: boolean }>(\"user-confirmed\", 10000);\n * if (result.confirmed) {\n * // proceed\n * }\n * } catch (e) {\n * console.log(\"User did not respond in time\");\n * }\n * ```\n */\nexport async function waitForEvent<T>(\n event: string,\n timeoutMs: number = 30000\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let unlisten: (() => void) | null = null;\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const cleanup = () => {\n if (unlisten) unlisten();\n clearTimeout(timeoutId);\n };\n\n timeoutId = setTimeout(() => {\n cleanup();\n reject(new Error(`Timeout waiting for event: ${event}`));\n }, timeoutMs);\n\n onEvent<T>(event, (payload) => {\n cleanup();\n resolve(payload);\n }).then((unlistenFn) => {\n unlisten = unlistenFn;\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["cachedPlatform: PlatformInfo | null","supportedPlatforms: PlatformKey[]","error","phase: \"detection\" | \"download\" | \"extraction\" | \"validation\"","cause?: Error","version: string | undefined","version: string","downloadUrl: string","error","cached: CachedRelease","unlisten: (() => void) | null","timeoutId: ReturnType<typeof setTimeout>"],"sources":["../src/utils/tauri.ts","../src/utils/messaging.ts","../src/utils/logger.ts","../src/utils/browser.ts","../src/utils/context.ts","../src/utils/filesystem.ts","../src/utils/plugin-storage.ts","../src/utils/http.ts","../src/utils/binary.ts","../src/utils/platform.ts","../src/utils/archive.ts","../src/utils/binary-resolver.ts","../src/utils/cookies.ts","../src/utils/window.ts","../src/utils/events.ts"],"sourcesContent":["/**\n * Tauri core utilities for plugin communication\n */\n\nexport interface TauriCore {\n invoke: <T>(cmd: string, args?: Record<string, unknown>) => Promise<T>;\n}\n\ninterface TauriWindow {\n __TAURI__?: {\n core: TauriCore;\n };\n}\n\n/**\n * Get the Tauri core API\n *\n * @deprecated Use higher-level APIs instead:\n * - File operations: `readFile`, `writeFile`, `listFiles`, `fileExists`\n * - HTTP: `fetchUrl`, `downloadAsset`\n * - Binary execution: `executeBinary`\n * - Cookies: `getPluginCookie`, `setPluginCookie`\n *\n * @throws Error if Tauri is not available\n */\nexport function getTauriCore(): TauriCore {\n const w = window as unknown as TauriWindow;\n if (!w.__TAURI__?.core) {\n throw new Error(\"Tauri core not available\");\n }\n return w.__TAURI__.core;\n}\n\n/**\n * Check if Tauri is available\n */\nexport function isTauriAvailable(): boolean {\n const w = window as unknown as TauriWindow;\n return !!w.__TAURI__?.core;\n}\n","/**\n * Plugin messaging utilities for communicating with Moss\n */\n\nimport type { PluginMessage } from \"../types/messages\";\nimport { getTauriCore, isTauriAvailable } from \"./tauri\";\n\nlet currentPluginName = \"\";\nlet currentHookName = \"\";\n\n/**\n * Set the message context for subsequent messages\n * This is typically called automatically by the plugin runtime\n */\nexport function setMessageContext(pluginName: string, hookName: string): void {\n currentPluginName = pluginName;\n currentHookName = hookName;\n}\n\n/**\n * Get the current message context\n */\nexport function getMessageContext(): { pluginName: string; hookName: string } {\n return { pluginName: currentPluginName, hookName: currentHookName };\n}\n\n/**\n * Send a message to Moss\n * Silently fails if Tauri is unavailable (useful for testing)\n */\nexport async function sendMessage(message: PluginMessage): Promise<void> {\n if (!isTauriAvailable()) {\n return;\n }\n\n try {\n await getTauriCore().invoke(\"plugin_message\", {\n pluginName: currentPluginName,\n hookName: currentHookName,\n message,\n });\n } catch {\n // Silently fail - logging would be recursive\n }\n}\n\n/**\n * Report progress to Moss\n */\nexport async function reportProgress(\n phase: string,\n current: number,\n total: number,\n message?: string\n): Promise<void> {\n await sendMessage({ type: \"progress\", phase, current, total, message });\n}\n\n/**\n * Report an error to Moss\n */\nexport async function reportError(\n error: string,\n context?: string,\n fatal = false\n): Promise<void> {\n await sendMessage({ type: \"error\", error, context, fatal });\n}\n\n/**\n * Report completion to Moss\n */\nexport async function reportComplete(result: unknown): Promise<void> {\n await sendMessage({ type: \"complete\", result });\n}\n","/**\n * Logging utilities for plugins\n */\n\nimport { sendMessage } from \"./messaging\";\n\n/**\n * Log an informational message\n */\nexport async function log(message: string): Promise<void> {\n await sendMessage({ type: \"log\", level: \"log\", message });\n}\n\n/**\n * Log a warning message\n */\nexport async function warn(message: string): Promise<void> {\n await sendMessage({ type: \"log\", level: \"warn\", message });\n}\n\n/**\n * Log an error message\n */\nexport async function error(message: string): Promise<void> {\n await sendMessage({ type: \"log\", level: \"error\", message });\n}\n","/**\n * Browser utilities for plugins\n * Abstracts Tauri browser commands to decouple plugins from internal APIs\n */\n\nimport { getTauriCore } from \"./tauri\";\n\n/**\n * Open a URL in the plugin browser window\n */\nexport async function openBrowser(url: string): Promise<void> {\n await getTauriCore().invoke(\"open_plugin_browser\", { url });\n}\n\n/**\n * Close the plugin browser window\n */\nexport async function closeBrowser(): Promise<void> {\n await getTauriCore().invoke(\"close_plugin_browser\", {});\n}\n","/**\n * Internal context utilities for Moss plugins\n *\n * This module provides access to the plugin execution context that is\n * set by the plugin runtime before each hook execution.\n *\n * INTERNAL USE ONLY - not exported to plugins.\n * Plugins should use the higher-level APIs (readFile, writeFile, etc.)\n * which use this context internally.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Internal plugin execution context\n *\n * Set by the plugin runtime (window.__MOSS_INTERNAL_CONTEXT__)\n * before each hook execution. Cleared after hook completes.\n */\nexport interface InternalPluginContext {\n /** Plugin name from manifest */\n plugin_name: string;\n /** Absolute path to the project directory */\n project_path: string;\n /** Absolute path to the .moss directory */\n moss_dir: string;\n}\n\n/**\n * Window interface with internal context\n */\ninterface ContextWindow {\n __MOSS_INTERNAL_CONTEXT__?: InternalPluginContext;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get the internal plugin context\n *\n * This is used internally by moss-api utilities to resolve paths\n * and plugin identity. Plugins should not call this directly.\n *\n * @returns The current plugin execution context\n * @throws Error if called outside of a plugin hook execution\n *\n * @internal\n */\nexport function getInternalContext(): InternalPluginContext {\n const context = (window as unknown as ContextWindow).__MOSS_INTERNAL_CONTEXT__;\n\n if (!context) {\n throw new Error(\n \"This function must be called from within a plugin hook. \" +\n \"Ensure you're calling this from process(), generate(), deploy(), or syndicate().\"\n );\n }\n\n return context;\n}\n\n/**\n * Check if we're currently inside a plugin hook execution\n *\n * @returns true if inside a hook, false otherwise\n *\n * @internal\n */\nexport function hasContext(): boolean {\n const context = (window as unknown as ContextWindow).__MOSS_INTERNAL_CONTEXT__;\n return context !== undefined;\n}\n","/**\n * File system operations for Moss plugins\n *\n * These functions provide access to project files (user content).\n * Project path is auto-detected from the runtime context.\n *\n * For plugin's private storage, use the plugin-storage API instead.\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Read a file from the project directory\n *\n * Project path is auto-detected from the runtime context.\n *\n * @param relativePath - Path relative to the project root\n * @returns File contents as a string\n * @throws Error if file cannot be read or called outside a hook\n *\n * @example\n * ```typescript\n * // Read an article\n * const content = await readFile(\"article/hello-world.md\");\n *\n * // Read package.json\n * const pkg = JSON.parse(await readFile(\"package.json\"));\n * ```\n */\nexport async function readFile(relativePath: string): Promise<string> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string>(\"read_project_file\", {\n projectPath: ctx.project_path,\n relativePath,\n });\n}\n\n/**\n * Write content to a file in the project directory\n *\n * Creates parent directories if they don't exist.\n * Project path is auto-detected from the runtime context.\n *\n * @param relativePath - Path relative to the project root\n * @param content - Content to write to the file\n * @throws Error if file cannot be written or called outside a hook\n *\n * @example\n * ```typescript\n * // Write a generated article\n * await writeFile(\"article/new-post.md\", \"# Hello World\\n\\nContent here.\");\n *\n * // Write index page\n * await writeFile(\"index.md\", markdownContent);\n * ```\n */\nexport async function writeFile(\n relativePath: string,\n content: string\n): Promise<void> {\n const ctx = getInternalContext();\n\n await getTauriCore().invoke(\"write_project_file\", {\n projectPath: ctx.project_path,\n relativePath,\n data: content,\n });\n}\n\n/**\n * List all files in the project directory\n *\n * Returns file paths relative to the project root.\n * Project path is auto-detected from the runtime context.\n *\n * @returns Array of relative file paths\n * @throws Error if directory cannot be listed or called outside a hook\n *\n * @example\n * ```typescript\n * const files = await listFiles();\n * // [\"index.md\", \"article/hello.md\", \"assets/logo.png\"]\n *\n * const mdFiles = files.filter(f => f.endsWith(\".md\"));\n * ```\n */\nexport async function listFiles(): Promise<string[]> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string[]>(\"list_project_files\", {\n projectPath: ctx.project_path,\n });\n}\n\n/**\n * Check if a file exists in the project directory\n *\n * Project path is auto-detected from the runtime context.\n *\n * @param relativePath - Path relative to the project root\n * @returns true if file exists, false otherwise\n * @throws Error if called outside a hook\n *\n * @example\n * ```typescript\n * if (await fileExists(\"index.md\")) {\n * const content = await readFile(\"index.md\");\n * }\n * ```\n */\nexport async function fileExists(relativePath: string): Promise<boolean> {\n // First, verify we have context (this will throw if not in a hook)\n getInternalContext();\n\n try {\n await readFile(relativePath);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Plugin storage API for Moss plugins\n *\n * Provides access to a plugin's private storage directory at:\n * .moss/plugins/{plugin-name}/\n *\n * Plugin identity is auto-detected from the runtime context -\n * plugins never need to know their own name or path.\n *\n * Config is just a file: readPluginFile(\"config.json\")\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Read a file from the plugin's private storage directory\n *\n * Storage path: .moss/plugins/{plugin-name}/{relativePath}\n *\n * @param relativePath - Path relative to the plugin's storage directory\n * @returns File contents as a string\n * @throws Error if file cannot be read or called outside a hook\n *\n * @example\n * ```typescript\n * // Read plugin config\n * const configJson = await readPluginFile(\"config.json\");\n * const config = JSON.parse(configJson);\n *\n * // Read cached data\n * const cached = await readPluginFile(\"cache/articles.json\");\n * ```\n */\nexport async function readPluginFile(relativePath: string): Promise<string> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string>(\"read_plugin_file\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n relativePath,\n });\n}\n\n/**\n * Write a file to the plugin's private storage directory\n *\n * Creates parent directories if they don't exist.\n * Storage path: .moss/plugins/{plugin-name}/{relativePath}\n *\n * @param relativePath - Path relative to the plugin's storage directory\n * @param content - Content to write to the file\n * @throws Error if file cannot be written or called outside a hook\n *\n * @example\n * ```typescript\n * // Save plugin config\n * await writePluginFile(\"config.json\", JSON.stringify(config, null, 2));\n *\n * // Cache data\n * await writePluginFile(\"cache/articles.json\", JSON.stringify(articles));\n * ```\n */\nexport async function writePluginFile(\n relativePath: string,\n content: string\n): Promise<void> {\n const ctx = getInternalContext();\n\n await getTauriCore().invoke(\"write_plugin_file\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n relativePath,\n content,\n });\n}\n\n/**\n * List all files in the plugin's private storage directory\n *\n * Returns file paths relative to the plugin's storage directory.\n *\n * @returns Array of relative file paths\n * @throws Error if directory cannot be listed or called outside a hook\n *\n * @example\n * ```typescript\n * const files = await listPluginFiles();\n * // [\"config.json\", \"cache/articles.json\", \"cache/images.json\"]\n * ```\n */\nexport async function listPluginFiles(): Promise<string[]> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<string[]>(\"list_plugin_files\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n });\n}\n\n/**\n * Check if a file exists in the plugin's private storage directory\n *\n * @param relativePath - Path relative to the plugin's storage directory\n * @returns true if file exists, false otherwise\n * @throws Error if called outside a hook\n *\n * @example\n * ```typescript\n * if (await pluginFileExists(\"config.json\")) {\n * const config = JSON.parse(await readPluginFile(\"config.json\"));\n * } else {\n * // Use default config\n * }\n * ```\n */\nexport async function pluginFileExists(relativePath: string): Promise<boolean> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<boolean>(\"plugin_file_exists\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n relativePath,\n });\n}\n","/**\n * HTTP operations for Moss plugins\n *\n * These functions provide HTTP capabilities that bypass browser CORS\n * restrictions by using Rust's HTTP client under the hood.\n *\n * Project path for downloads is auto-detected from the runtime context.\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for HTTP fetch requests\n */\nexport interface FetchOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n}\n\n/**\n * Result from an HTTP fetch operation\n */\nexport interface FetchResult {\n /** HTTP status code */\n status: number;\n /** Whether the request was successful (2xx status) */\n ok: boolean;\n /** Content-Type header from response */\n contentType: string | null;\n /** Response body as Uint8Array */\n body: Uint8Array;\n /** Get response body as text */\n text(): string;\n}\n\n/**\n * Options for asset download\n */\nexport interface DownloadOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n}\n\n/**\n * Result from an asset download operation\n */\nexport interface DownloadResult {\n /** HTTP status code */\n status: number;\n /** Whether the request was successful (2xx status) */\n ok: boolean;\n /** Content-Type header from response */\n contentType: string | null;\n /** Number of bytes written to disk */\n bytesWritten: number;\n /** Actual path where file was saved (relative to project) */\n actualPath: string;\n}\n\n// ============================================================================\n// Internal Types (Tauri response shapes)\n// ============================================================================\n\ninterface TauriFetchResult {\n status: number;\n ok: boolean;\n body_base64: string;\n content_type: string | null;\n}\n\ninterface TauriDownloadResult {\n status: number;\n ok: boolean;\n content_type: string | null;\n bytes_written: number;\n actual_path: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Fetch a URL using Rust's HTTP client (bypasses CORS)\n *\n * @param url - URL to fetch\n * @param options - Optional fetch configuration\n * @returns Fetch result with status, body, and helpers\n * @throws Error if network request fails\n *\n * @example\n * ```typescript\n * const result = await fetchUrl(\"https://api.example.com/data\");\n * if (result.ok) {\n * const data = JSON.parse(result.text());\n * }\n * ```\n */\nexport async function fetchUrl(\n url: string,\n options: FetchOptions = {}\n): Promise<FetchResult> {\n const { timeoutMs = 30000 } = options;\n\n const result = await getTauriCore().invoke<TauriFetchResult>(\"fetch_url\", {\n url,\n timeoutMs,\n });\n\n // Decode base64 body to Uint8Array\n const binaryString = atob(result.body_base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n return {\n status: result.status,\n ok: result.ok,\n contentType: result.content_type,\n body: bytes,\n text(): string {\n return new TextDecoder().decode(bytes);\n },\n };\n}\n\n/**\n * Options for HTTP POST requests\n */\nexport interface PostOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeoutMs?: number;\n /** Additional headers */\n headers?: Record<string, string>;\n}\n\n/**\n * Perform an HTTP POST request with JSON body\n *\n * Uses Rust's HTTP client to bypass browser CORS restrictions.\n * This is useful for OAuth flows and other API interactions.\n *\n * @param url - URL to POST to\n * @param body - JSON object to send as the request body\n * @param options - Optional configuration including timeout and headers\n * @returns Fetch result with status, body, and helpers\n * @throws Error if network request fails\n *\n * @example\n * ```typescript\n * // GitHub OAuth device code request\n * const result = await httpPost(\n * \"https://github.com/login/device/code\",\n * { client_id: \"xxx\", scope: \"repo workflow\" },\n * { headers: { Accept: \"application/json\" } }\n * );\n * if (result.ok) {\n * const data = JSON.parse(result.text());\n * }\n * ```\n */\nexport async function httpPost(\n url: string,\n body: Record<string, unknown>,\n options: PostOptions = {}\n): Promise<FetchResult> {\n const { timeoutMs = 30000, headers = {} } = options;\n\n const result = await getTauriCore().invoke<TauriFetchResult>(\"http_post\", {\n url,\n body: JSON.stringify(body),\n headers,\n timeoutMs,\n });\n\n // Decode base64 body to Uint8Array\n const binaryString = atob(result.body_base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n return {\n status: result.status,\n ok: result.ok,\n contentType: result.content_type,\n body: bytes,\n text(): string {\n return new TextDecoder().decode(bytes);\n },\n };\n}\n\n/**\n * Download a URL and save directly to disk\n *\n * Downloads the file and writes it directly to disk without passing\n * the binary data through JavaScript. The filename is derived from\n * the URL, and file extension is inferred from Content-Type if needed.\n *\n * Project path is auto-detected from the runtime context.\n *\n * @param url - URL to download\n * @param targetDir - Target directory within project (e.g., \"assets\")\n * @param options - Optional download configuration\n * @returns Download result with actual path where file was saved\n * @throws Error if download or write fails, or called outside a hook\n *\n * @example\n * ```typescript\n * const result = await downloadAsset(\n * \"https://example.com/image\",\n * \"assets\"\n * );\n * if (result.ok) {\n * console.log(`Saved to ${result.actualPath}`); // e.g., \"assets/image.png\"\n * }\n * ```\n */\nexport async function downloadAsset(\n url: string,\n targetDir: string,\n options: DownloadOptions = {}\n): Promise<DownloadResult> {\n const ctx = getInternalContext();\n const { timeoutMs = 30000 } = options;\n\n const result = await getTauriCore().invoke<TauriDownloadResult>(\n \"download_asset\",\n {\n url,\n projectPath: ctx.project_path,\n targetDir,\n timeoutMs,\n }\n );\n\n return {\n status: result.status,\n ok: result.ok,\n contentType: result.content_type,\n bytesWritten: result.bytes_written,\n actualPath: result.actual_path,\n };\n}\n","/**\n * Binary execution for Moss plugins\n *\n * Allows plugins to execute external binaries (git, npm, etc.)\n * in a controlled environment.\n *\n * Working directory is auto-detected from the runtime context\n * (always the project root).\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for executing a binary\n */\nexport interface ExecuteOptions {\n /** Path to the binary (can be just the name if in PATH) */\n binaryPath: string;\n /** Arguments to pass to the binary */\n args: string[];\n /** Timeout in milliseconds (default: 60000) */\n timeoutMs?: number;\n /** Additional environment variables */\n env?: Record<string, string>;\n}\n\n/**\n * Result from binary execution\n */\nexport interface ExecuteResult {\n /** Whether the command succeeded (exit code 0) */\n success: boolean;\n /** Exit code from the process */\n exitCode: number;\n /** Standard output from the process */\n stdout: string;\n /** Standard error output from the process */\n stderr: string;\n}\n\n// ============================================================================\n// Internal Types (Tauri response shape)\n// ============================================================================\n\ninterface TauriBinaryResult {\n success: boolean;\n exit_code: number;\n stdout: string;\n stderr: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Execute an external binary\n *\n * Working directory is auto-detected from the runtime context\n * (always the project root).\n *\n * @param options - Execution options including binary path and args\n * @returns Execution result with stdout, stderr, and exit code\n * @throws Error if binary cannot be executed or called outside a hook\n *\n * @example\n * ```typescript\n * // Run git status\n * const result = await executeBinary({\n * binaryPath: \"git\",\n * args: [\"status\"],\n * });\n *\n * if (result.success) {\n * console.log(result.stdout);\n * } else {\n * console.error(result.stderr);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Run npm install with timeout\n * const result = await executeBinary({\n * binaryPath: \"npm\",\n * args: [\"install\"],\n * timeoutMs: 120000,\n * env: { NODE_ENV: \"production\" },\n * });\n * ```\n */\nexport async function executeBinary(\n options: ExecuteOptions\n): Promise<ExecuteResult> {\n const ctx = getInternalContext();\n const { binaryPath, args, timeoutMs = 60000, env } = options;\n\n const result = await getTauriCore().invoke<TauriBinaryResult>(\n \"execute_binary\",\n {\n binaryPath,\n args,\n workingDir: ctx.project_path,\n timeoutMs,\n env,\n }\n );\n\n return {\n success: result.success,\n exitCode: result.exit_code,\n stdout: result.stdout,\n stderr: result.stderr,\n };\n}\n","/**\n * Platform detection utilities for Moss plugins\n *\n * Detects the current operating system and architecture to enable\n * platform-specific binary downloads and operations.\n */\n\nimport { executeBinary } from \"./binary\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Supported operating systems\n */\nexport type OSType = \"darwin\" | \"linux\" | \"windows\";\n\n/**\n * Supported architectures\n */\nexport type ArchType = \"arm64\" | \"x64\";\n\n/**\n * Platform key combining OS and architecture\n */\nexport type PlatformKey =\n | \"darwin-arm64\"\n | \"darwin-x64\"\n | \"linux-x64\"\n | \"windows-x64\";\n\n/**\n * Complete platform information\n */\nexport interface PlatformInfo {\n /** Operating system */\n os: OSType;\n /** CPU architecture */\n arch: ArchType;\n /** Combined platform key for binary selection */\n platformKey: PlatformKey;\n}\n\n// ============================================================================\n// Cached Detection\n// ============================================================================\n\nlet cachedPlatform: PlatformInfo | null = null;\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Detect the current platform (OS and architecture)\n *\n * Uses system commands to detect the platform:\n * - On macOS/Linux: `uname -s` for OS, `uname -m` for architecture\n * - On Windows: Falls back to environment variables and defaults\n *\n * Results are cached after the first call.\n *\n * @returns Platform information including OS, architecture, and combined key\n * @throws Error if platform detection fails or platform is unsupported\n *\n * @example\n * ```typescript\n * const platform = await getPlatformInfo();\n * console.log(platform.platformKey); // \"darwin-arm64\"\n * ```\n */\nexport async function getPlatformInfo(): Promise<PlatformInfo> {\n // Return cached result if available\n if (cachedPlatform) {\n return cachedPlatform;\n }\n\n const os = await detectOS();\n const arch = await detectArch(os);\n\n // Validate supported platform combinations\n const platformKey = `${os}-${arch}` as PlatformKey;\n const supportedPlatforms: PlatformKey[] = [\n \"darwin-arm64\",\n \"darwin-x64\",\n \"linux-x64\",\n \"windows-x64\",\n ];\n\n if (!supportedPlatforms.includes(platformKey)) {\n throw new Error(\n `Unsupported platform: ${platformKey}. ` +\n `Supported platforms: ${supportedPlatforms.join(\", \")}`\n );\n }\n\n cachedPlatform = { os, arch, platformKey };\n return cachedPlatform;\n}\n\n/**\n * Clear the cached platform info\n *\n * Useful for testing or when platform detection needs to be re-run.\n *\n * @internal\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n}\n\n// ============================================================================\n// Internal Detection Functions\n// ============================================================================\n\n/**\n * Detect the operating system\n */\nasync function detectOS(): Promise<OSType> {\n try {\n // Try uname first (works on macOS and Linux)\n const result = await executeBinary({\n binaryPath: \"uname\",\n args: [\"-s\"],\n timeoutMs: 5000,\n });\n\n if (result.success) {\n const osName = result.stdout.trim().toLowerCase();\n if (osName === \"darwin\") {\n return \"darwin\";\n }\n if (osName === \"linux\") {\n return \"linux\";\n }\n }\n } catch {\n // uname not available, likely Windows\n }\n\n // Check for Windows via cmd\n try {\n const result = await executeBinary({\n binaryPath: \"cmd\",\n args: [\"/c\", \"ver\"],\n timeoutMs: 5000,\n });\n\n if (result.success && result.stdout.toLowerCase().includes(\"windows\")) {\n return \"windows\";\n }\n } catch {\n // Not Windows either\n }\n\n throw new Error(\n \"Unable to detect operating system. \" +\n \"Supported systems: macOS (Darwin), Linux, Windows\"\n );\n}\n\n/**\n * Detect the CPU architecture\n */\nasync function detectArch(os: OSType): Promise<ArchType> {\n if (os === \"windows\") {\n // On Windows, check PROCESSOR_ARCHITECTURE environment variable\n try {\n const result = await executeBinary({\n binaryPath: \"cmd\",\n args: [\"/c\", \"echo\", \"%PROCESSOR_ARCHITECTURE%\"],\n timeoutMs: 5000,\n });\n\n if (result.success) {\n const arch = result.stdout.trim().toLowerCase();\n if (arch === \"arm64\") {\n return \"arm64\";\n }\n // AMD64, x86_64, etc. -> x64\n return \"x64\";\n }\n } catch {\n // Default to x64 on Windows\n return \"x64\";\n }\n }\n\n // Unix-like systems (macOS, Linux)\n try {\n const result = await executeBinary({\n binaryPath: \"uname\",\n args: [\"-m\"],\n timeoutMs: 5000,\n });\n\n if (result.success) {\n const machine = result.stdout.trim().toLowerCase();\n if (machine === \"arm64\" || machine === \"aarch64\") {\n return \"arm64\";\n }\n if (machine === \"x86_64\" || machine === \"amd64\") {\n return \"x64\";\n }\n // Fallback for other architectures\n if (machine.includes(\"arm\")) {\n return \"arm64\";\n }\n return \"x64\";\n }\n } catch {\n // Fallback\n }\n\n // Default to x64 if detection fails\n return \"x64\";\n}\n","/**\n * Archive extraction utilities for Moss plugins\n *\n * Provides functions to extract .tar.gz and .zip archives using\n * system commands (tar, unzip, PowerShell).\n */\n\nimport { executeBinary } from \"./binary\";\nimport { getPlatformInfo } from \"./platform\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Supported archive formats\n */\nexport type ArchiveFormat = \"tar.gz\" | \"zip\";\n\n/**\n * Options for archive extraction\n */\nexport interface ExtractOptions {\n /** Path to the archive file (absolute) */\n archivePath: string;\n /** Directory to extract to (absolute) */\n destDir: string;\n /** Archive format (auto-detected from extension if not provided) */\n format?: ArchiveFormat;\n /** Timeout in milliseconds (default: 60000) */\n timeoutMs?: number;\n}\n\n/**\n * Result of archive extraction\n */\nexport interface ExtractResult {\n /** Whether extraction succeeded */\n success: boolean;\n /** Error message if extraction failed */\n error?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Extract an archive to a destination directory\n *\n * Uses system commands for extraction:\n * - .tar.gz: `tar -xzf` (macOS/Linux)\n * - .zip: `unzip` (macOS/Linux) or PowerShell `Expand-Archive` (Windows)\n *\n * @param options - Extraction options\n * @returns Extraction result\n *\n * @example\n * ```typescript\n * const result = await extractArchive({\n * archivePath: \"/path/to/hugo.tar.gz\",\n * destDir: \"/path/to/extract/\",\n * });\n *\n * if (!result.success) {\n * console.error(`Extraction failed: ${result.error}`);\n * }\n * ```\n */\nexport async function extractArchive(\n options: ExtractOptions\n): Promise<ExtractResult> {\n const { archivePath, destDir, timeoutMs = 60000 } = options;\n\n // Auto-detect format from extension if not provided\n const format = options.format ?? detectFormat(archivePath);\n\n if (!format) {\n return {\n success: false,\n error: `Unable to detect archive format for: ${archivePath}. Supported formats: .tar.gz, .zip`,\n };\n }\n\n const platform = await getPlatformInfo();\n\n try {\n if (format === \"tar.gz\") {\n return await extractTarGz(archivePath, destDir, timeoutMs);\n } else {\n // zip format\n if (platform.os === \"windows\") {\n return await extractZipWindows(archivePath, destDir, timeoutMs);\n } else {\n return await extractZipUnix(archivePath, destDir, timeoutMs);\n }\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Make a file executable (Unix only)\n *\n * Runs `chmod +x` on the specified file. No-op on Windows.\n *\n * @param filePath - Absolute path to the file\n * @returns Whether the operation succeeded\n *\n * @example\n * ```typescript\n * await makeExecutable(\"/path/to/binary\");\n * ```\n */\nexport async function makeExecutable(filePath: string): Promise<boolean> {\n const platform = await getPlatformInfo();\n\n // No-op on Windows - executables don't need chmod\n if (platform.os === \"windows\") {\n return true;\n }\n\n try {\n const result = await executeBinary({\n binaryPath: \"chmod\",\n args: [\"+x\", filePath],\n timeoutMs: 5000,\n });\n\n return result.success;\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Internal Functions\n// ============================================================================\n\n/**\n * Detect archive format from file extension\n */\nfunction detectFormat(archivePath: string): ArchiveFormat | null {\n const lowerPath = archivePath.toLowerCase();\n\n if (lowerPath.endsWith(\".tar.gz\") || lowerPath.endsWith(\".tgz\")) {\n return \"tar.gz\";\n }\n if (lowerPath.endsWith(\".zip\")) {\n return \"zip\";\n }\n\n return null;\n}\n\n/**\n * Extract .tar.gz using tar command\n */\nasync function extractTarGz(\n archivePath: string,\n destDir: string,\n timeoutMs: number\n): Promise<ExtractResult> {\n const result = await executeBinary({\n binaryPath: \"tar\",\n args: [\"-xzf\", archivePath, \"-C\", destDir],\n timeoutMs,\n });\n\n if (!result.success) {\n return {\n success: false,\n error: result.stderr || `tar extraction failed with exit code ${result.exitCode}`,\n };\n }\n\n return { success: true };\n}\n\n/**\n * Extract .zip using unzip command (macOS/Linux)\n */\nasync function extractZipUnix(\n archivePath: string,\n destDir: string,\n timeoutMs: number\n): Promise<ExtractResult> {\n const result = await executeBinary({\n binaryPath: \"unzip\",\n args: [\"-o\", \"-q\", archivePath, \"-d\", destDir],\n timeoutMs,\n });\n\n if (!result.success) {\n return {\n success: false,\n error: result.stderr || `unzip extraction failed with exit code ${result.exitCode}`,\n };\n }\n\n return { success: true };\n}\n\n/**\n * Extract .zip using PowerShell (Windows)\n */\nasync function extractZipWindows(\n archivePath: string,\n destDir: string,\n timeoutMs: number\n): Promise<ExtractResult> {\n // Use PowerShell's Expand-Archive cmdlet\n const command = `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`;\n\n const result = await executeBinary({\n binaryPath: \"powershell\",\n args: [\"-NoProfile\", \"-NonInteractive\", \"-Command\", command],\n timeoutMs,\n });\n\n if (!result.success) {\n return {\n success: false,\n error: result.stderr || `PowerShell extraction failed with exit code ${result.exitCode}`,\n };\n }\n\n return { success: true };\n}\n","/**\n * Binary resolver for Moss plugins\n *\n * Provides auto-detection and download of external CLI tools like Hugo.\n * Implements a 4-step resolution flow:\n * 1. Check configured path (from plugin config)\n * 2. Check system PATH\n * 3. Check plugin bin directory\n * 4. Download from GitHub releases (if enabled)\n */\n\nimport { executeBinary } from \"./binary\";\nimport { fetchUrl } from \"./http\";\nimport { pluginFileExists, writePluginFile, readPluginFile } from \"./plugin-storage\";\nimport { getInternalContext } from \"./context\";\nimport { getPlatformInfo, type PlatformKey } from \"./platform\";\nimport { extractArchive, makeExecutable } from \"./archive\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * GitHub source configuration for binary downloads\n */\nexport interface GitHubSource {\n /** Repository owner (e.g., \"gohugoio\") */\n owner: string;\n /** Repository name (e.g., \"hugo\") */\n repo: string;\n /**\n * Asset filename pattern with placeholders:\n * - {version}: Version number (e.g., \"0.139.0\")\n * - {os}: Operating system (darwin, linux, windows)\n * - {arch}: Architecture (arm64, amd64, x64)\n *\n * Example: \"hugo_extended_{version}_{os}-{arch}.tar.gz\"\n */\n assetPattern: string;\n}\n\n/**\n * Binary source configuration per platform\n */\nexport interface BinarySource {\n /** GitHub release source */\n github?: GitHubSource;\n /** Direct download URL (with same placeholders as assetPattern) */\n directUrl?: string;\n}\n\n/**\n * Configuration for a binary to resolve\n */\nexport interface BinaryConfig {\n /** Binary name (e.g., \"hugo\") */\n name: string;\n /** Minimum version required (semver, optional) */\n minVersion?: string;\n /** Command to check version (default: \"{name} version\") */\n versionCommand?: string;\n /** Regex to extract version from command output */\n versionPattern?: RegExp;\n /** Download sources per platform */\n sources: Partial<Record<PlatformKey, BinarySource>>;\n /** Binary filename inside archive (default: same as name, or name.exe on Windows) */\n binaryName?: string;\n}\n\n/**\n * Result of binary resolution\n */\nexport interface BinaryResolution {\n /** Absolute path to the binary */\n path: string;\n /** Detected version (if available) */\n version?: string;\n /** How the binary was found */\n source: \"config\" | \"path\" | \"plugin-storage\" | \"downloaded\";\n}\n\n/**\n * Options for binary resolution\n */\nexport interface ResolveBinaryOptions {\n /** Plugin's configured binary path (from context.config) */\n configuredPath?: string;\n /** Whether to auto-download if not found (default: true) */\n autoDownload?: boolean;\n /** Progress callback for UI feedback */\n onProgress?: (phase: string, message: string) => void;\n}\n\n/**\n * Error thrown during binary resolution\n */\nexport class BinaryResolutionError extends Error {\n constructor(\n message: string,\n public readonly phase: \"detection\" | \"download\" | \"extraction\" | \"validation\",\n public readonly cause?: Error\n ) {\n super(message);\n this.name = \"BinaryResolutionError\";\n }\n}\n\n// ============================================================================\n// Main Resolution Function\n// ============================================================================\n\n/**\n * Resolve a binary, downloading if necessary\n *\n * Resolution order:\n * 1. Configured path (from plugin config, e.g., hugo_path)\n * 2. System PATH (just the binary name)\n * 3. Plugin bin directory (.moss/plugins/{plugin}/bin/{name})\n * 4. Download from GitHub releases (if autoDownload is true)\n *\n * @param config - Binary configuration\n * @param options - Resolution options\n * @returns Resolution result with path and source\n * @throws BinaryResolutionError if binary cannot be resolved\n *\n * @example\n * ```typescript\n * const hugo = await resolveBinary(HUGO_CONFIG, {\n * configuredPath: context.config.hugo_path,\n * onProgress: (phase, msg) => reportProgress(phase, 0, 1, msg),\n * });\n *\n * await executeBinary({\n * binaryPath: hugo.path,\n * args: [\"--version\"],\n * });\n * ```\n */\nexport async function resolveBinary(\n config: BinaryConfig,\n options: ResolveBinaryOptions = {}\n): Promise<BinaryResolution> {\n const { configuredPath, autoDownload = true, onProgress } = options;\n\n const progress = (phase: string, message: string) => {\n onProgress?.(phase, message);\n };\n\n // Step 1: Check configured path\n if (configuredPath) {\n progress(\"detection\", `Checking configured path: ${configuredPath}`);\n const result = await checkBinary(configuredPath, config);\n if (result) {\n return { path: configuredPath, version: result.version, source: \"config\" };\n }\n }\n\n // Step 2: Check system PATH\n progress(\"detection\", `Checking system PATH for ${config.name}`);\n const pathResult = await checkBinary(config.name, config);\n if (pathResult) {\n return { path: config.name, version: pathResult.version, source: \"path\" };\n }\n\n // Step 3: Check plugin bin directory\n const pluginBinPath = await getPluginBinPath(config);\n progress(\"detection\", `Checking plugin storage: ${pluginBinPath}`);\n\n if (await binaryExistsInPluginStorage(config)) {\n const storedResult = await checkBinary(pluginBinPath, config);\n if (storedResult) {\n return { path: pluginBinPath, version: storedResult.version, source: \"plugin-storage\" };\n }\n }\n\n // Step 4: Download if enabled\n if (!autoDownload) {\n throw new BinaryResolutionError(\n `${config.name} not found. ` +\n `Please install it manually or set the path in plugin configuration.\\n\\n` +\n `Installation options:\\n` +\n `- Install via package manager (brew, apt, etc.)\\n` +\n `- Download from the official website\\n` +\n `- Set ${config.name}_path in .moss/config.toml`,\n \"detection\"\n );\n }\n\n progress(\"download\", `Downloading ${config.name}...`);\n const downloadedPath = await downloadBinary(config, progress);\n\n // Verify the downloaded binary works\n const downloadedResult = await checkBinary(downloadedPath, config);\n if (!downloadedResult) {\n throw new BinaryResolutionError(\n `Downloaded ${config.name} binary failed verification. ` +\n `The binary may be corrupted or incompatible with your system.`,\n \"validation\"\n );\n }\n\n return {\n path: downloadedPath,\n version: downloadedResult.version,\n source: \"downloaded\",\n };\n}\n\n// ============================================================================\n// Binary Checking\n// ============================================================================\n\ninterface CheckResult {\n version?: string;\n}\n\n/**\n * Check if a binary exists and optionally extract its version\n */\nasync function checkBinary(\n binaryPath: string,\n config: BinaryConfig\n): Promise<CheckResult | null> {\n try {\n // Build version command\n const versionCmd = config.versionCommand ?? `${config.name} version`;\n const [cmd, ...args] = parseCommand(versionCmd, binaryPath, config.name);\n\n const result = await executeBinary({\n binaryPath: cmd,\n args,\n timeoutMs: 10000,\n });\n\n if (!result.success) {\n return null;\n }\n\n // Extract version if pattern provided\n let version: string | undefined;\n if (config.versionPattern) {\n const output = result.stdout + result.stderr;\n const match = output.match(config.versionPattern);\n if (match && match[1]) {\n version = match[1];\n }\n }\n\n return { version };\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a version command, replacing {name} with the binary path\n */\nfunction parseCommand(\n template: string,\n binaryPath: string,\n name: string\n): string[] {\n // Replace {name} with actual binary path\n const resolved = template.replace(/{name}/g, binaryPath);\n\n // If the template is just \"{name} version\", use the binaryPath directly\n if (resolved.startsWith(binaryPath)) {\n const rest = resolved.slice(binaryPath.length).trim();\n return [binaryPath, ...rest.split(/\\s+/).filter(Boolean)];\n }\n\n // Otherwise split normally\n return resolved.split(/\\s+/).filter(Boolean);\n}\n\n// ============================================================================\n// Plugin Storage Helpers\n// ============================================================================\n\n/**\n * Get the full path to the binary in plugin storage\n */\nasync function getPluginBinPath(config: BinaryConfig): Promise<string> {\n const ctx = getInternalContext();\n const platform = await getPlatformInfo();\n\n const binaryName = getBinaryFilename(config, platform.os === \"windows\");\n return `${ctx.moss_dir}/plugins/${ctx.plugin_name}/bin/${binaryName}`;\n}\n\n/**\n * Get the binary filename (with .exe on Windows)\n */\nfunction getBinaryFilename(config: BinaryConfig, isWindows: boolean): string {\n const baseName = config.binaryName ?? config.name;\n return isWindows ? `${baseName}.exe` : baseName;\n}\n\n/**\n * Check if binary exists in plugin storage\n */\nasync function binaryExistsInPluginStorage(config: BinaryConfig): Promise<boolean> {\n const platform = await getPlatformInfo();\n const binaryName = getBinaryFilename(config, platform.os === \"windows\");\n return pluginFileExists(`bin/${binaryName}`);\n}\n\n// ============================================================================\n// Download Logic\n// ============================================================================\n\n/**\n * Download and extract binary from GitHub releases\n */\nasync function downloadBinary(\n config: BinaryConfig,\n progress: (phase: string, message: string) => void\n): Promise<string> {\n const platform = await getPlatformInfo();\n const source = config.sources[platform.platformKey];\n\n if (!source) {\n throw new BinaryResolutionError(\n `No download source configured for platform: ${platform.platformKey}`,\n \"download\"\n );\n }\n\n // Get latest version from GitHub\n let version: string;\n let downloadUrl: string;\n\n if (source.github) {\n progress(\"download\", \"Fetching latest release info from GitHub...\");\n const releaseInfo = await getLatestRelease(source.github.owner, source.github.repo);\n version = releaseInfo.version;\n\n // Construct download URL\n const assetName = resolveAssetPattern(source.github.assetPattern, version, platform);\n downloadUrl = `https://github.com/${source.github.owner}/${source.github.repo}/releases/download/v${version}/${assetName}`;\n } else if (source.directUrl) {\n // Use direct URL - version is extracted from URL if possible\n downloadUrl = source.directUrl;\n\n // Try to extract version from URL (e.g., \"/v0.152.2/\" or \"_0.152.2_\")\n const versionMatch = downloadUrl.match(/[/v_](\\d+\\.\\d+\\.\\d+)[/_]/);\n version = versionMatch ? versionMatch[1] : \"unknown\";\n\n progress(\"download\", `Using direct download URL (v${version})...`);\n } else {\n throw new BinaryResolutionError(\n `No download source configured for ${config.name}`,\n \"download\"\n );\n }\n\n progress(\"download\", `Downloading ${config.name} v${version}...`);\n\n // Download to temp location\n const ctx = getInternalContext();\n const archiveFilename = downloadUrl.split(\"/\").pop() ?? \"archive\";\n const archivePath = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/.tmp/${archiveFilename}`;\n\n await downloadToPluginStorage(downloadUrl, `.tmp/${archiveFilename}`);\n\n // Extract archive\n progress(\"extraction\", \"Extracting archive...\");\n const binDir = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/bin`;\n\n // Ensure bin directory exists by writing a marker file\n await writePluginFile(\"bin/.gitkeep\", \"\");\n\n const extractResult = await extractArchive({\n archivePath,\n destDir: binDir,\n });\n\n if (!extractResult.success) {\n throw new BinaryResolutionError(\n `Failed to extract archive: ${extractResult.error}`,\n \"extraction\"\n );\n }\n\n // Make binary executable (Unix only)\n const binaryPath = await getPluginBinPath(config);\n await makeExecutable(binaryPath);\n\n // Clean up temp archive\n // Note: We don't have a delete API, so we just leave it for now\n // The temp file will be overwritten on next download\n\n progress(\"complete\", `${config.name} v${version} installed successfully`);\n\n // Cache the version info\n await cacheReleaseInfo(config.name, version);\n\n return binaryPath;\n}\n\n/**\n * Fetch latest release info from GitHub API\n */\nasync function getLatestRelease(\n owner: string,\n repo: string\n): Promise<{ version: string; tag: string }> {\n // Try to use cached info if GitHub API fails\n const cacheKey = `${owner}/${repo}`;\n\n try {\n const response = await fetchUrl(\n `https://api.github.com/repos/${owner}/${repo}/releases/latest`,\n { timeoutMs: 10000 }\n );\n\n if (!response.ok) {\n // Check for rate limiting\n if (response.status === 403 || response.status === 429) {\n const cached = await getCachedRelease(cacheKey);\n if (cached) {\n return cached;\n }\n throw new BinaryResolutionError(\n \"GitHub API rate limit exceeded. Please try again later or install the binary manually.\",\n \"download\"\n );\n }\n throw new BinaryResolutionError(\n `Failed to fetch release info: HTTP ${response.status}`,\n \"download\"\n );\n }\n\n const data = JSON.parse(response.text());\n const tag = data.tag_name as string;\n const version = tag.replace(/^v/, \"\"); // \"v0.139.0\" -> \"0.139.0\"\n\n return { version, tag };\n } catch (error) {\n if (error instanceof BinaryResolutionError) {\n throw error;\n }\n\n // Try cache on any error\n const cached = await getCachedRelease(cacheKey);\n if (cached) {\n return cached;\n }\n\n throw new BinaryResolutionError(\n `Failed to fetch release info: ${error instanceof Error ? error.message : String(error)}`,\n \"download\",\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Resolve asset pattern with actual values\n */\nfunction resolveAssetPattern(\n pattern: string,\n version: string,\n platform: { os: string; arch: string }\n): string {\n // Map our arch names to common GitHub naming conventions\n const archMap: Record<string, string> = {\n x64: \"amd64\",\n arm64: \"arm64\",\n };\n\n return pattern\n .replace(/{version}/g, version)\n .replace(/{os}/g, platform.os)\n .replace(/{arch}/g, archMap[platform.arch] ?? platform.arch);\n}\n\n/**\n * Download a file to plugin storage\n *\n * Uses curl (macOS/Linux) or PowerShell (Windows) to download the file\n * directly to the target path, avoiding memory limitations of base64 encoding.\n */\nasync function downloadToPluginStorage(\n url: string,\n relativePath: string\n): Promise<void> {\n const ctx = getInternalContext();\n const platform = await getPlatformInfo();\n\n const targetPath = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/${relativePath}`;\n\n // Ensure parent directory exists\n const parentDir = targetPath.substring(0, targetPath.lastIndexOf(\"/\"));\n\n if (platform.os === \"windows\") {\n // Use PowerShell for Windows\n const mkdirCmd = `New-Item -ItemType Directory -Force -Path '${parentDir}'`;\n await executeBinary({\n binaryPath: \"powershell\",\n args: [\"-NoProfile\", \"-NonInteractive\", \"-Command\", mkdirCmd],\n timeoutMs: 5000,\n });\n\n const downloadCmd = `Invoke-WebRequest -Uri '${url}' -OutFile '${targetPath}'`;\n const result = await executeBinary({\n binaryPath: \"powershell\",\n args: [\"-NoProfile\", \"-NonInteractive\", \"-Command\", downloadCmd],\n timeoutMs: 300000, // 5 minutes\n });\n\n if (!result.success) {\n throw new BinaryResolutionError(\n `Download failed: ${result.stderr || result.stdout}`,\n \"download\"\n );\n }\n } else {\n // Use curl for macOS/Linux (pre-installed on both)\n await executeBinary({\n binaryPath: \"mkdir\",\n args: [\"-p\", parentDir],\n timeoutMs: 5000,\n });\n\n const result = await executeBinary({\n binaryPath: \"curl\",\n args: [\n \"-fsSL\", // fail silently, follow redirects, show errors\n \"--create-dirs\",\n \"-o\", targetPath,\n url,\n ],\n timeoutMs: 300000, // 5 minutes\n });\n\n if (!result.success) {\n throw new BinaryResolutionError(\n `Download failed: ${result.stderr || `curl exited with code ${result.exitCode}`}`,\n \"download\"\n );\n }\n }\n}\n\n// ============================================================================\n// Caching\n// ============================================================================\n\ninterface CachedRelease {\n version: string;\n tag: string;\n cachedAt: string;\n}\n\n/**\n * Cache release info for fallback\n */\nasync function cacheReleaseInfo(name: string, version: string): Promise<void> {\n try {\n const cached: CachedRelease = {\n version,\n tag: `v${version}`,\n cachedAt: new Date().toISOString(),\n };\n await writePluginFile(\n `cache/${name}-release.json`,\n JSON.stringify(cached, null, 2)\n );\n } catch {\n // Ignore cache write failures\n }\n}\n\n/**\n * Get cached release info\n */\nasync function getCachedRelease(\n cacheKey: string\n): Promise<{ version: string; tag: string } | null> {\n try {\n // Extract name from cache key (owner/repo -> repo)\n const name = cacheKey.split(\"/\")[1];\n if (!name) return null;\n\n if (!(await pluginFileExists(`cache/${name}-release.json`))) {\n return null;\n }\n\n const content = await readPluginFile(`cache/${name}-release.json`);\n const cached = JSON.parse(content) as CachedRelease;\n\n return { version: cached.version, tag: cached.tag };\n } catch {\n return null;\n }\n}\n","/**\n * Cookie management for Moss plugins\n *\n * Allows plugins to store and retrieve authentication cookies\n * for external services (e.g., Matters.town, GitHub).\n *\n * Cookies are automatically scoped to the plugin's registered domain\n * (defined in manifest.json) - plugins cannot access other plugins' cookies.\n */\n\nimport { getTauriCore } from \"./tauri\";\nimport { getInternalContext } from \"./context\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A cookie stored for plugin authentication\n */\nexport interface Cookie {\n /** Cookie name */\n name: string;\n /** Cookie value */\n value: string;\n /** Optional domain for the cookie */\n domain?: string;\n /** Optional path for the cookie */\n path?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get stored cookies for the current plugin.\n *\n * The plugin's identity is automatically detected from the runtime context.\n * Cookies are filtered by the domain declared in the plugin's manifest.json.\n *\n * **Must be called from within a plugin hook** (process, generate, deploy, syndicate).\n *\n * @returns Array of cookies for the plugin's registered domain\n * @throws Error if called outside of a plugin hook execution\n *\n * @example\n * ```typescript\n * // Inside a hook function:\n * const cookies = await getPluginCookie();\n * const token = cookies.find(c => c.name === \"__access_token\");\n * if (token) {\n * // Use token for authenticated requests\n * }\n * ```\n */\nexport async function getPluginCookie(): Promise<Cookie[]> {\n const ctx = getInternalContext();\n\n return getTauriCore().invoke<Cookie[]>(\"get_plugin_cookie\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n });\n}\n\n/**\n * Store cookies for the current plugin.\n *\n * The plugin's identity is automatically detected from the runtime context.\n *\n * **Must be called from within a plugin hook** (process, generate, deploy, syndicate).\n *\n * @param cookies - Array of cookies to store\n * @throws Error if called outside of a plugin hook execution\n *\n * @example\n * ```typescript\n * // Inside a hook function:\n * await setPluginCookie([\n * { name: \"session\", value: \"abc123\" }\n * ]);\n * ```\n */\nexport async function setPluginCookie(cookies: Cookie[]): Promise<void> {\n const ctx = getInternalContext();\n\n await getTauriCore().invoke(\"set_plugin_cookie\", {\n pluginName: ctx.plugin_name,\n projectPath: ctx.project_path,\n cookies,\n });\n}\n","/**\n * Window utilities for plugins\n * Enables plugins to show custom dialogs and UI elements\n */\n\nimport { getTauriCore } from \"./tauri\";\n\n/**\n * Result from a dialog interaction\n */\nexport interface DialogResult {\n type: \"submitted\" | \"cancelled\";\n value?: unknown;\n}\n\n/**\n * Options for showing a plugin dialog\n */\nexport interface ShowDialogOptions {\n /** URL to load in the dialog (can be data: URL with embedded HTML) */\n url: string;\n /** Dialog window title */\n title: string;\n /** Dialog width in pixels */\n width?: number;\n /** Dialog height in pixels */\n height?: number;\n /** Maximum time to wait for user response in milliseconds */\n timeoutMs?: number;\n}\n\n/**\n * Show a plugin dialog and wait for user response\n *\n * The dialog can be an embedded HTML page (via data: URL) that communicates\n * back to the plugin via the submitDialogResult function.\n *\n * @param options - Dialog configuration\n * @returns Dialog result with submitted value or cancellation\n *\n * @example\n * ```typescript\n * const result = await showPluginDialog({\n * url: createMyDialogUrl(),\n * title: \"Create Repository\",\n * width: 400,\n * height: 300,\n * });\n *\n * if (result.type === \"submitted\") {\n * console.log(\"User submitted:\", result.value);\n * } else {\n * console.log(\"User cancelled\");\n * }\n * ```\n */\nexport async function showPluginDialog(\n options: ShowDialogOptions\n): Promise<DialogResult> {\n const result = await getTauriCore().invoke<DialogResult>(\n \"show_plugin_dialog\",\n {\n url: options.url,\n title: options.title,\n width: options.width ?? 500,\n height: options.height ?? 400,\n timeoutMs: options.timeoutMs ?? 300000, // 5 minutes default\n }\n );\n return result;\n}\n\n/**\n * Submit a result from within a plugin dialog\n *\n * This is called from inside the dialog HTML to send data back to the plugin.\n * The dialog will be closed automatically after submission.\n *\n * @param dialogId - The dialog ID (provided in the dialog's query string)\n * @param value - The value to submit\n * @returns Whether the submission was successful\n *\n * @example\n * ```typescript\n * // Inside dialog HTML:\n * const dialogId = new URLSearchParams(location.search).get('dialogId');\n * await submitDialogResult(dialogId, { repoName: 'my-repo' });\n * ```\n */\nexport async function submitDialogResult(\n dialogId: string,\n value: unknown\n): Promise<boolean> {\n return getTauriCore().invoke<boolean>(\"submit_dialog_result\", {\n dialogId,\n result: { type: \"submitted\", value },\n });\n}\n\n/**\n * Cancel a plugin dialog\n *\n * This is called from inside the dialog HTML to cancel without submitting.\n * The dialog will be closed automatically.\n *\n * @param dialogId - The dialog ID (provided in the dialog's query string)\n *\n * @example\n * ```typescript\n * // Inside dialog HTML:\n * const dialogId = new URLSearchParams(location.search).get('dialogId');\n * await cancelDialog(dialogId);\n * ```\n */\nexport async function cancelDialog(dialogId: string): Promise<boolean> {\n return getTauriCore().invoke<boolean>(\"submit_dialog_result\", {\n dialogId,\n result: { type: \"cancelled\" },\n });\n}\n","/**\n * Event utilities for plugin communication\n *\n * Provides a way for plugins and dialog windows to communicate\n * via Tauri's event system.\n */\n\n/**\n * Tauri event listener interface\n */\ninterface TauriEventListen {\n (event: string, handler: (event: { payload: unknown }) => void): Promise<() => void>;\n}\n\n/**\n * Tauri event emit interface\n */\ninterface TauriEventEmit {\n (event: string, payload?: unknown): Promise<void>;\n}\n\ninterface TauriEventWindow {\n __TAURI__?: {\n event?: {\n listen: TauriEventListen;\n emit: TauriEventEmit;\n };\n };\n}\n\n/**\n * Get Tauri event API\n * @internal\n */\nfunction getTauriEvent(): { listen: TauriEventListen; emit: TauriEventEmit } {\n const w = window as unknown as TauriEventWindow;\n if (!w.__TAURI__?.event) {\n throw new Error(\"Tauri event API not available\");\n }\n return w.__TAURI__.event;\n}\n\n/**\n * Check if Tauri event API is available\n */\nexport function isEventApiAvailable(): boolean {\n const w = window as unknown as TauriEventWindow;\n return !!w.__TAURI__?.event;\n}\n\n/**\n * Emit an event to other parts of the application\n *\n * @param event - Event name (e.g., \"repo-created\", \"dialog-result\")\n * @param payload - Data to send with the event\n *\n * @example\n * ```typescript\n * // From dialog:\n * await emitEvent(\"repo-name-validated\", { name: \"my-repo\", available: true });\n *\n * // From plugin:\n * await emitEvent(\"deployment-started\", { url: \"https://github.com/...\" });\n * ```\n */\nexport async function emitEvent(event: string, payload?: unknown): Promise<void> {\n await getTauriEvent().emit(event, payload);\n}\n\n/**\n * Listen for events from other parts of the application\n *\n * @param event - Event name to listen for\n * @param handler - Function to call when event is received\n * @returns Cleanup function to stop listening\n *\n * @example\n * ```typescript\n * const unlisten = await onEvent<{ name: string; available: boolean }>(\n * \"repo-name-validated\",\n * (data) => {\n * console.log(`Repo ${data.name} is ${data.available ? \"available\" : \"taken\"}`);\n * }\n * );\n *\n * // Later, to stop listening:\n * unlisten();\n * ```\n */\nexport async function onEvent<T>(\n event: string,\n handler: (payload: T) => void\n): Promise<() => void> {\n const unlisten = await getTauriEvent().listen(event, (e) => {\n handler(e.payload as T);\n });\n return unlisten;\n}\n\n/**\n * Wait for a single event occurrence\n *\n * @param event - Event name to wait for\n * @param timeoutMs - Maximum time to wait (default: 30000ms)\n * @returns Promise that resolves with the event payload\n * @throws Error if timeout is reached\n *\n * @example\n * ```typescript\n * try {\n * const result = await waitForEvent<{ confirmed: boolean }>(\"user-confirmed\", 10000);\n * if (result.confirmed) {\n * // proceed\n * }\n * } catch (e) {\n * console.log(\"User did not respond in time\");\n * }\n * ```\n */\nexport async function waitForEvent<T>(\n event: string,\n timeoutMs: number = 30000\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let unlisten: (() => void) | null = null;\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const cleanup = () => {\n if (unlisten) unlisten();\n clearTimeout(timeoutId);\n };\n\n timeoutId = setTimeout(() => {\n cleanup();\n reject(new Error(`Timeout waiting for event: ${event}`));\n }, timeoutMs);\n\n onEvent<T>(event, (payload) => {\n cleanup();\n resolve(payload);\n }).then((unlistenFn) => {\n unlisten = unlistenFn;\n });\n });\n}\n"],"mappings":"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"}
|
package/dist/testing/index.mjs
CHANGED
|
@@ -349,6 +349,22 @@ function setupMockTauri(options) {
|
|
|
349
349
|
content_type: response.contentType || null
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
|
+
case "http_post": {
|
|
353
|
+
const url = payload?.url;
|
|
354
|
+
const response = urlConfig.getResponse(url);
|
|
355
|
+
if (response.delay) return new Promise((resolve) => setTimeout(() => resolve({
|
|
356
|
+
status: response.status,
|
|
357
|
+
ok: response.ok,
|
|
358
|
+
body_base64: response.bodyBase64 || "",
|
|
359
|
+
content_type: response.contentType || null
|
|
360
|
+
}), response.delay));
|
|
361
|
+
return {
|
|
362
|
+
status: response.status,
|
|
363
|
+
ok: response.ok,
|
|
364
|
+
body_base64: response.bodyBase64 || "",
|
|
365
|
+
content_type: response.contentType || null
|
|
366
|
+
};
|
|
367
|
+
}
|
|
352
368
|
case "download_asset": {
|
|
353
369
|
const url = payload?.url;
|
|
354
370
|
const targetDir = payload?.targetDir;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["completedDownloads: string[]","failedDownloads: Array<{ url: string; error: string }>","defaultResponse: MockUrlResponse","defaultResult: MockBinaryResult","openedUrls: string[]","shownDialogs: Array<{ url: string; title: string; width: number; height: number }>","nextResult: MockDialogResult | null","projectPath","pluginName"],"sources":["../../src/testing/mock-tauri.ts"],"sourcesContent":["/**\n * Tauri IPC mocking utilities for testing Moss plugins\n *\n * Provides in-memory implementations of Tauri IPC commands that plugins use\n * through moss-api. This enables integration testing without a running Tauri app.\n *\n * @example\n * ```typescript\n * import { setupMockTauri } from \"@symbiosis-lab/moss-api/testing\";\n *\n * describe(\"my plugin\", () => {\n * let ctx: MockTauriContext;\n *\n * beforeEach(() => {\n * ctx = setupMockTauri();\n * });\n *\n * afterEach(() => {\n * ctx.cleanup();\n * });\n *\n * it(\"reads files\", async () => {\n * ctx.filesystem.setFile(\"/project/test.md\", \"# Hello\");\n * const content = await readFile(\"/project\", \"test.md\");\n * expect(content).toBe(\"# Hello\");\n * });\n * });\n * ```\n */\n\n// Define minimal types for invoke args\ninterface InvokeArgs {\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Mock Filesystem\n// ============================================================================\n\n/**\n * A file stored in the mock filesystem\n */\nexport interface MockFile {\n content: string;\n createdAt: Date;\n modifiedAt: Date;\n}\n\n/**\n * In-memory filesystem for testing file operations\n */\nexport interface MockFilesystem {\n /** Internal file storage */\n files: Map<string, MockFile>;\n /** Get a file by full path */\n getFile(path: string): MockFile | undefined;\n /** Set a file's content (creates or updates) */\n setFile(path: string, content: string): void;\n /** Delete a file */\n deleteFile(path: string): boolean;\n /** List files matching an optional pattern */\n listFiles(pattern?: string): string[];\n /** Clear all files */\n clear(): void;\n}\n\n/**\n * Create a new mock filesystem instance\n */\nexport function createMockFilesystem(): MockFilesystem {\n const files = new Map<string, MockFile>();\n\n return {\n files,\n getFile(path: string) {\n return files.get(path);\n },\n setFile(path: string, content: string) {\n const now = new Date();\n const existing = files.get(path);\n files.set(path, {\n content,\n createdAt: existing?.createdAt ?? now,\n modifiedAt: now,\n });\n },\n deleteFile(path: string) {\n return files.delete(path);\n },\n listFiles(pattern?: string) {\n const allPaths = Array.from(files.keys());\n if (!pattern) return allPaths;\n // Simple glob matching\n const regex = new RegExp(\n \"^\" + pattern.replace(/\\*/g, \".*\").replace(/\\?/g, \".\") + \"$\"\n );\n return allPaths.filter((p) => regex.test(p));\n },\n clear() {\n files.clear();\n },\n };\n}\n\n// ============================================================================\n// Download Tracker\n// ============================================================================\n\n/**\n * Tracks download activity for testing concurrency and completion\n */\nexport interface DownloadTracker {\n /** Number of currently active downloads */\n activeDownloads: number;\n /** Maximum concurrent downloads observed */\n maxConcurrent: number;\n /** URLs of completed downloads */\n completedDownloads: string[];\n /** Failed downloads with error messages */\n failedDownloads: Array<{ url: string; error: string }>;\n /** Mark a download as started */\n startDownload(url: string): void;\n /** Mark a download as ended */\n endDownload(url: string, success: boolean, error?: string): void;\n /** Reset all tracking state */\n reset(): void;\n}\n\n/**\n * Create a new download tracker instance\n */\nexport function createDownloadTracker(): DownloadTracker {\n let activeDownloads = 0;\n let maxConcurrent = 0;\n const completedDownloads: string[] = [];\n const failedDownloads: Array<{ url: string; error: string }> = [];\n\n return {\n get activeDownloads() {\n return activeDownloads;\n },\n get maxConcurrent() {\n return maxConcurrent;\n },\n get completedDownloads() {\n return completedDownloads;\n },\n get failedDownloads() {\n return failedDownloads;\n },\n startDownload(url: string) {\n activeDownloads++;\n if (activeDownloads > maxConcurrent) {\n maxConcurrent = activeDownloads;\n }\n },\n endDownload(url: string, success: boolean, error?: string) {\n activeDownloads--;\n if (success) {\n completedDownloads.push(url);\n } else {\n failedDownloads.push({ url, error: error || \"Unknown error\" });\n }\n },\n reset() {\n activeDownloads = 0;\n maxConcurrent = 0;\n completedDownloads.length = 0;\n failedDownloads.length = 0;\n },\n };\n}\n\n// ============================================================================\n// URL Response Configuration\n// ============================================================================\n\n/**\n * Configuration for a mocked URL response\n */\nexport interface MockUrlResponse {\n /** HTTP status code */\n status: number;\n /** Whether the request was successful (2xx) */\n ok: boolean;\n /** Content-Type header */\n contentType?: string;\n /** Response body as base64 (for fetch_url) */\n bodyBase64?: string;\n /** Number of bytes written (for download_asset) */\n bytesWritten?: number;\n /** Actual file path where asset was saved */\n actualPath?: string;\n /** Artificial delay in milliseconds */\n delay?: number;\n}\n\n/**\n * URL response configuration for mocking HTTP requests\n */\nexport interface MockUrlConfig {\n /** Map of URL to response(s) */\n responses: Map<string, MockUrlResponse | MockUrlResponse[]>;\n /** Default response for unregistered URLs */\n defaultResponse: MockUrlResponse;\n /** Set response for a URL (can be single or array for retry testing) */\n setResponse(url: string, response: MockUrlResponse | MockUrlResponse[]): void;\n /** Get response for a URL (handles retry sequences) */\n getResponse(url: string): MockUrlResponse;\n /** Reset all URL configurations */\n reset(): void;\n}\n\n/**\n * Create a new URL config instance\n */\nexport function createMockUrlConfig(): MockUrlConfig {\n const responses = new Map<string, MockUrlResponse | MockUrlResponse[]>();\n const callCounts = new Map<string, number>();\n\n // Default: 1x1 red PNG\n const defaultResponse: MockUrlResponse = {\n status: 200,\n ok: true,\n contentType: \"image/png\",\n bodyBase64:\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==\",\n bytesWritten: 68,\n actualPath: \"assets/image.png\",\n };\n\n return {\n responses,\n defaultResponse,\n setResponse(url: string, response: MockUrlResponse | MockUrlResponse[]) {\n responses.set(url, response);\n callCounts.set(url, 0);\n },\n getResponse(url: string): MockUrlResponse {\n const config = responses.get(url);\n if (!config) return defaultResponse;\n\n if (Array.isArray(config)) {\n const count = callCounts.get(url) || 0;\n callCounts.set(url, count + 1);\n // Return the response at the current index, or the last one if exceeded\n return config[Math.min(count, config.length - 1)];\n }\n\n return config;\n },\n reset() {\n responses.clear();\n callCounts.clear();\n },\n };\n}\n\n// ============================================================================\n// Binary Execution Tracker\n// ============================================================================\n\n/**\n * Result for a mocked binary execution\n */\nexport interface MockBinaryResult {\n success: boolean;\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * Configuration for mocking binary execution\n */\nexport interface MockBinaryConfig {\n /** Map of binary commands to results */\n results: Map<string, MockBinaryResult>;\n /** Default result for unregistered binaries */\n defaultResult: MockBinaryResult;\n /** Set result for a binary command (key format: \"binaryPath args...\") */\n setResult(key: string, result: MockBinaryResult): void;\n /** Get result for a binary command */\n getResult(binaryPath: string, args: string[]): MockBinaryResult;\n /** Reset all configurations */\n reset(): void;\n}\n\n/**\n * Create a new binary config instance\n */\nexport function createMockBinaryConfig(): MockBinaryConfig {\n const results = new Map<string, MockBinaryResult>();\n\n const defaultResult: MockBinaryResult = {\n success: true,\n exitCode: 0,\n stdout: \"\",\n stderr: \"\",\n };\n\n return {\n results,\n defaultResult,\n setResult(key: string, result: MockBinaryResult) {\n results.set(key, result);\n },\n getResult(binaryPath: string, args: string[]): MockBinaryResult {\n // Try exact match first\n const exactKey = `${binaryPath} ${args.join(\" \")}`.trim();\n if (results.has(exactKey)) {\n return results.get(exactKey)!;\n }\n // Try binary name only\n if (results.has(binaryPath)) {\n return results.get(binaryPath)!;\n }\n return defaultResult;\n },\n reset() {\n results.clear();\n },\n };\n}\n\n// ============================================================================\n// Cookie Storage\n// ============================================================================\n\n/**\n * Mock cookie storage for plugin authentication testing\n */\nexport interface MockCookieStorage {\n /** Map of pluginName:projectPath to cookies */\n cookies: Map<string, Array<{ name: string; value: string; domain?: string; path?: string }>>;\n /** Get cookies for a plugin/project */\n getCookies(\n pluginName: string,\n projectPath: string\n ): Array<{ name: string; value: string; domain?: string; path?: string }>;\n /** Set cookies for a plugin/project */\n setCookies(\n pluginName: string,\n projectPath: string,\n cookies: Array<{ name: string; value: string; domain?: string; path?: string }>\n ): void;\n /** Clear all cookies */\n clear(): void;\n}\n\n/**\n * Create a new cookie storage instance\n */\nexport function createMockCookieStorage(): MockCookieStorage {\n const cookies = new Map<\n string,\n Array<{ name: string; value: string; domain?: string; path?: string }>\n >();\n\n return {\n cookies,\n getCookies(pluginName: string, projectPath: string) {\n const key = `${pluginName}:${projectPath}`;\n return cookies.get(key) || [];\n },\n setCookies(\n pluginName: string,\n projectPath: string,\n newCookies: Array<{ name: string; value: string; domain?: string; path?: string }>\n ) {\n const key = `${pluginName}:${projectPath}`;\n cookies.set(key, newCookies);\n },\n clear() {\n cookies.clear();\n },\n };\n}\n\n// ============================================================================\n// Browser Tracker\n// ============================================================================\n\n/**\n * Tracks browser open/close calls for testing\n */\nexport interface MockBrowserTracker {\n /** URLs that were opened */\n openedUrls: string[];\n /** Number of times closeBrowser was called */\n closeCount: number;\n /** Whether browser is currently open */\n isOpen: boolean;\n /** Reset tracking state */\n reset(): void;\n}\n\n/**\n * Create a new browser tracker instance\n */\nexport function createMockBrowserTracker(): MockBrowserTracker {\n const openedUrls: string[] = [];\n let closeCount = 0;\n let isOpen = false;\n\n return {\n get openedUrls() {\n return openedUrls;\n },\n get closeCount() {\n return closeCount;\n },\n set closeCount(value: number) {\n closeCount = value;\n },\n get isOpen() {\n return isOpen;\n },\n set isOpen(value: boolean) {\n isOpen = value;\n },\n reset() {\n openedUrls.length = 0;\n closeCount = 0;\n isOpen = false;\n },\n };\n}\n\n// ============================================================================\n// Dialog Tracker\n// ============================================================================\n\n/**\n * Dialog result types matching the moss backend\n */\nexport interface MockDialogResult {\n type: \"submitted\" | \"cancelled\";\n value?: unknown;\n}\n\n/**\n * Tracks dialog interactions for testing\n */\nexport interface MockDialogTracker {\n /** Dialogs that were shown (with their URLs and titles) */\n shownDialogs: Array<{ url: string; title: string; width: number; height: number }>;\n /** Configure the next dialog result (for automatic response) */\n nextResult: MockDialogResult | null;\n /** Submitted results by dialog ID */\n submittedResults: Map<string, MockDialogResult>;\n /** Set the result for the next dialog shown */\n setNextResult(result: MockDialogResult): void;\n /** Simulate user submitting a dialog result */\n submitResult(dialogId: string, value: unknown): void;\n /** Simulate user cancelling a dialog */\n cancelDialog(dialogId: string): void;\n /** Reset tracking state */\n reset(): void;\n}\n\n/**\n * Create a new dialog tracker instance\n */\nexport function createMockDialogTracker(): MockDialogTracker {\n const shownDialogs: Array<{ url: string; title: string; width: number; height: number }> = [];\n const submittedResults = new Map<string, MockDialogResult>();\n let nextResult: MockDialogResult | null = null;\n\n return {\n get shownDialogs() {\n return shownDialogs;\n },\n get nextResult() {\n return nextResult;\n },\n set nextResult(value: MockDialogResult | null) {\n nextResult = value;\n },\n get submittedResults() {\n return submittedResults;\n },\n setNextResult(result: MockDialogResult) {\n nextResult = result;\n },\n submitResult(dialogId: string, value: unknown) {\n submittedResults.set(dialogId, { type: \"submitted\", value });\n },\n cancelDialog(dialogId: string) {\n submittedResults.set(dialogId, { type: \"cancelled\" });\n },\n reset() {\n shownDialogs.length = 0;\n submittedResults.clear();\n nextResult = null;\n },\n };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Extract filename from URL (mimics Rust backend behavior)\n */\nfunction extractFilenameFromUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const segments = pathname.split(\"/\").filter((s) => s.length > 0);\n\n // Try to find UUID in path\n for (const segment of segments) {\n if (\n /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(\n segment\n )\n ) {\n return `${segment}.png`; // Default to PNG for mock\n }\n }\n\n // Fallback to last segment or hash\n const lastSegment = segments[segments.length - 1] || \"image\";\n return lastSegment.includes(\".\") ? lastSegment : `${lastSegment}.png`;\n } catch {\n return \"image.png\";\n }\n}\n\n// ============================================================================\n// Main Setup Function\n// ============================================================================\n\n/**\n * Options for setting up mock Tauri environment\n */\nexport interface SetupMockTauriOptions {\n /** Plugin name for internal context (default: \"test-plugin\") */\n pluginName?: string;\n /** Project path for internal context (default: \"/test/project\") */\n projectPath?: string;\n}\n\n/**\n * Context returned by setupMockTauri with all mock utilities\n */\nexport interface MockTauriContext {\n /** In-memory filesystem */\n filesystem: MockFilesystem;\n /** Download tracking for concurrency tests */\n downloadTracker: DownloadTracker;\n /** URL response configuration */\n urlConfig: MockUrlConfig;\n /** Binary execution configuration */\n binaryConfig: MockBinaryConfig;\n /** Cookie storage */\n cookieStorage: MockCookieStorage;\n /** Browser open/close tracking */\n browserTracker: MockBrowserTracker;\n /** Dialog interaction tracking */\n dialogTracker: MockDialogTracker;\n /** The project path used for internal context */\n projectPath: string;\n /** The plugin name used for internal context */\n pluginName: string;\n /** Cleanup function - must be called after tests */\n cleanup: () => void;\n}\n\n/**\n * Set up mock Tauri IPC for testing\n *\n * This sets up `window.__TAURI__.core.invoke` to intercept all IPC calls\n * and route them to in-memory implementations. It also sets up\n * `__MOSS_INTERNAL_CONTEXT__` for the context-aware APIs.\n *\n * @param options - Optional configuration for project path and plugin name\n * @returns Context with mock utilities and cleanup function\n *\n * @example\n * ```typescript\n * const ctx = setupMockTauri({ projectPath: \"/my/project\", pluginName: \"my-plugin\" });\n *\n * // Set up test data\n * ctx.filesystem.setFile(\"/my/project/article.md\", \"# Test\");\n * ctx.urlConfig.setResponse(\"https://example.com/image.png\", {\n * status: 200,\n * ok: true,\n * contentType: \"image/png\",\n * bytesWritten: 1024,\n * });\n *\n * // Run your plugin code...\n *\n * // Verify results\n * expect(ctx.downloadTracker.completedDownloads).toHaveLength(1);\n *\n * // Cleanup\n * ctx.cleanup();\n * ```\n */\nexport function setupMockTauri(options?: SetupMockTauriOptions): MockTauriContext {\n const projectPath = options?.projectPath ?? \"/test/project\";\n const pluginName = options?.pluginName ?? \"test-plugin\";\n\n const filesystem = createMockFilesystem();\n const downloadTracker = createDownloadTracker();\n const urlConfig = createMockUrlConfig();\n const binaryConfig = createMockBinaryConfig();\n const cookieStorage = createMockCookieStorage();\n const browserTracker = createMockBrowserTracker();\n const dialogTracker = createMockDialogTracker();\n\n // Create invoke handler\n const invoke = async (cmd: string, args?: InvokeArgs): Promise<unknown> => {\n const payload = args as Record<string, unknown> | undefined;\n\n switch (cmd) {\n // ======================================================================\n // Filesystem Operations\n // ======================================================================\n case \"read_project_file\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const fullPath = `${projectPath}/${relativePath}`;\n const file = filesystem.getFile(fullPath);\n if (file) {\n return file.content;\n }\n throw new Error(`File not found: ${fullPath}`);\n }\n\n case \"write_project_file\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const content = payload?.data as string; // Note: moss-api uses 'data' not 'content'\n const fullPath = `${projectPath}/${relativePath}`;\n filesystem.setFile(fullPath, content);\n return null;\n }\n\n case \"list_project_files\": {\n const projectPath = payload?.projectPath as string;\n // Return all file paths relative to the project path\n const allPaths = filesystem.listFiles();\n return allPaths\n .filter((p) => p.startsWith(projectPath + \"/\"))\n .map((p) => p.substring(projectPath.length + 1));\n }\n\n case \"project_file_exists\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const fullPath = `${projectPath}/${relativePath}`;\n return filesystem.getFile(fullPath) !== undefined;\n }\n\n // ======================================================================\n // Plugin Storage Operations\n // ======================================================================\n case \"read_plugin_file\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const rp = payload?.relativePath as string;\n const fullPath = `${pp}/.moss/plugins/${pn}/${rp}`;\n const file = filesystem.getFile(fullPath);\n if (file) {\n return file.content;\n }\n throw new Error(`Plugin file not found: ${fullPath}`);\n }\n\n case \"write_plugin_file\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const rp = payload?.relativePath as string;\n const content = payload?.content as string;\n const fullPath = `${pp}/.moss/plugins/${pn}/${rp}`;\n filesystem.setFile(fullPath, content);\n return null;\n }\n\n case \"list_plugin_files\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const prefix = `${pp}/.moss/plugins/${pn}/`;\n const allPaths = filesystem.listFiles();\n return allPaths\n .filter((p) => p.startsWith(prefix))\n .map((p) => p.substring(prefix.length));\n }\n\n case \"plugin_file_exists\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const rp = payload?.relativePath as string;\n const fullPath = `${pp}/.moss/plugins/${pn}/${rp}`;\n return filesystem.getFile(fullPath) !== undefined;\n }\n\n // ======================================================================\n // HTTP Operations\n // ======================================================================\n case \"fetch_url\": {\n const url = payload?.url as string;\n const response = urlConfig.getResponse(url);\n\n if (response.delay) {\n return new Promise((resolve) =>\n setTimeout(\n () =>\n resolve({\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n }),\n response.delay\n )\n );\n }\n\n return {\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n };\n }\n\n case \"download_asset\": {\n const url = payload?.url as string;\n const targetDir = payload?.targetDir as string;\n const response = urlConfig.getResponse(url);\n\n downloadTracker.startDownload(url);\n\n // status 0 simulates a network error/timeout - throw an error\n if (response.status === 0) {\n downloadTracker.endDownload(url, false, \"Network error\");\n throw new Error(\"Network timeout\");\n }\n\n // Generate actual_path based on URL or use configured value\n const actualPath =\n response.actualPath || `${targetDir}/${extractFilenameFromUrl(url)}`;\n\n const result = {\n status: response.status,\n ok: response.ok,\n content_type: response.contentType || null,\n bytes_written: response.bytesWritten || 0,\n actual_path: actualPath,\n };\n\n if (response.delay) {\n return new Promise((resolve) =>\n setTimeout(() => {\n downloadTracker.endDownload(url, response.ok);\n resolve(result);\n }, response.delay)\n );\n }\n\n downloadTracker.endDownload(url, response.ok);\n return result;\n }\n\n // ======================================================================\n // Cookie Operations\n // ======================================================================\n case \"get_plugin_cookie\": {\n const pluginName = payload?.pluginName as string;\n const projectPath = payload?.projectPath as string;\n return cookieStorage.getCookies(pluginName, projectPath);\n }\n\n case \"set_plugin_cookie\": {\n const pluginName = payload?.pluginName as string;\n const projectPath = payload?.projectPath as string;\n const cookies = payload?.cookies as Array<{\n name: string;\n value: string;\n domain?: string;\n path?: string;\n }>;\n cookieStorage.setCookies(pluginName, projectPath, cookies);\n return null;\n }\n\n // ======================================================================\n // Browser Operations\n // ======================================================================\n case \"open_plugin_browser\": {\n const url = payload?.url as string;\n browserTracker.openedUrls.push(url);\n (browserTracker as { isOpen: boolean }).isOpen = true;\n return null;\n }\n\n case \"close_plugin_browser\": {\n (browserTracker as { closeCount: number }).closeCount++;\n (browserTracker as { isOpen: boolean }).isOpen = false;\n return null;\n }\n\n // ======================================================================\n // Dialog Operations\n // ======================================================================\n case \"show_plugin_dialog\": {\n const url = payload?.url as string;\n const title = payload?.title as string;\n const width = (payload?.width as number) ?? 500;\n const height = (payload?.height as number) ?? 400;\n\n // Track the dialog\n (dialogTracker.shownDialogs as Array<{ url: string; title: string; width: number; height: number }>).push({\n url,\n title,\n width,\n height,\n });\n\n // If a next result is configured, return it immediately\n if (dialogTracker.nextResult) {\n const result = dialogTracker.nextResult;\n (dialogTracker as { nextResult: MockDialogResult | null }).nextResult = null;\n return result;\n }\n\n // Default to cancelled\n return { type: \"cancelled\" };\n }\n\n case \"submit_dialog_result\": {\n const dialogId = payload?.dialogId as string;\n const result = payload?.result as MockDialogResult;\n\n if (result) {\n dialogTracker.submittedResults.set(dialogId, result);\n }\n\n return true;\n }\n\n // ======================================================================\n // Binary Execution\n // ======================================================================\n case \"execute_binary\": {\n const binaryPath = payload?.binaryPath as string;\n const binaryArgs = payload?.args as string[];\n const result = binaryConfig.getResult(binaryPath, binaryArgs);\n\n return {\n success: result.success,\n exit_code: result.exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n };\n }\n\n // ======================================================================\n // Messaging (silent no-op)\n // ======================================================================\n case \"plugin_message\": {\n // Silently accept plugin messages (logs, progress, errors, etc.)\n return null;\n }\n\n default:\n console.warn(`Unhandled IPC command: ${cmd}`);\n return null;\n }\n };\n\n // Set up window.__TAURI__ directly (moss-api checks for this)\n const w = globalThis as unknown as {\n window?: {\n __TAURI__?: { core?: { invoke: typeof invoke } };\n __MOSS_INTERNAL_CONTEXT__?: {\n plugin_name: string;\n project_path: string;\n moss_dir: string;\n };\n };\n };\n\n // Ensure window exists (for Node.js environments like happy-dom)\n if (typeof w.window === \"undefined\") {\n (globalThis as unknown as { window: object }).window = {};\n }\n\n const win = (globalThis as unknown as {\n window: {\n __TAURI__?: { core?: { invoke: typeof invoke } };\n __MOSS_INTERNAL_CONTEXT__?: {\n plugin_name: string;\n project_path: string;\n moss_dir: string;\n };\n };\n }).window;\n\n win.__TAURI__ = {\n core: { invoke },\n };\n\n // Set up internal context for context-aware APIs\n win.__MOSS_INTERNAL_CONTEXT__ = {\n plugin_name: pluginName,\n project_path: projectPath,\n moss_dir: `${projectPath}/.moss`,\n };\n\n return {\n filesystem,\n downloadTracker,\n urlConfig,\n binaryConfig,\n cookieStorage,\n browserTracker,\n dialogTracker,\n projectPath,\n pluginName,\n cleanup: () => {\n // Clear the mock Tauri interface and internal context\n delete win.__TAURI__;\n delete win.__MOSS_INTERNAL_CONTEXT__;\n filesystem.clear();\n downloadTracker.reset();\n urlConfig.reset();\n binaryConfig.reset();\n cookieStorage.clear();\n browserTracker.reset();\n dialogTracker.reset();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["completedDownloads: string[]","failedDownloads: Array<{ url: string; error: string }>","defaultResponse: MockUrlResponse","defaultResult: MockBinaryResult","openedUrls: string[]","shownDialogs: Array<{ url: string; title: string; width: number; height: number }>","nextResult: MockDialogResult | null","projectPath","pluginName"],"sources":["../../src/testing/mock-tauri.ts"],"sourcesContent":["/**\n * Tauri IPC mocking utilities for testing Moss plugins\n *\n * Provides in-memory implementations of Tauri IPC commands that plugins use\n * through moss-api. This enables integration testing without a running Tauri app.\n *\n * @example\n * ```typescript\n * import { setupMockTauri } from \"@symbiosis-lab/moss-api/testing\";\n *\n * describe(\"my plugin\", () => {\n * let ctx: MockTauriContext;\n *\n * beforeEach(() => {\n * ctx = setupMockTauri();\n * });\n *\n * afterEach(() => {\n * ctx.cleanup();\n * });\n *\n * it(\"reads files\", async () => {\n * ctx.filesystem.setFile(\"/project/test.md\", \"# Hello\");\n * const content = await readFile(\"/project\", \"test.md\");\n * expect(content).toBe(\"# Hello\");\n * });\n * });\n * ```\n */\n\n// Define minimal types for invoke args\ninterface InvokeArgs {\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Mock Filesystem\n// ============================================================================\n\n/**\n * A file stored in the mock filesystem\n */\nexport interface MockFile {\n content: string;\n createdAt: Date;\n modifiedAt: Date;\n}\n\n/**\n * In-memory filesystem for testing file operations\n */\nexport interface MockFilesystem {\n /** Internal file storage */\n files: Map<string, MockFile>;\n /** Get a file by full path */\n getFile(path: string): MockFile | undefined;\n /** Set a file's content (creates or updates) */\n setFile(path: string, content: string): void;\n /** Delete a file */\n deleteFile(path: string): boolean;\n /** List files matching an optional pattern */\n listFiles(pattern?: string): string[];\n /** Clear all files */\n clear(): void;\n}\n\n/**\n * Create a new mock filesystem instance\n */\nexport function createMockFilesystem(): MockFilesystem {\n const files = new Map<string, MockFile>();\n\n return {\n files,\n getFile(path: string) {\n return files.get(path);\n },\n setFile(path: string, content: string) {\n const now = new Date();\n const existing = files.get(path);\n files.set(path, {\n content,\n createdAt: existing?.createdAt ?? now,\n modifiedAt: now,\n });\n },\n deleteFile(path: string) {\n return files.delete(path);\n },\n listFiles(pattern?: string) {\n const allPaths = Array.from(files.keys());\n if (!pattern) return allPaths;\n // Simple glob matching\n const regex = new RegExp(\n \"^\" + pattern.replace(/\\*/g, \".*\").replace(/\\?/g, \".\") + \"$\"\n );\n return allPaths.filter((p) => regex.test(p));\n },\n clear() {\n files.clear();\n },\n };\n}\n\n// ============================================================================\n// Download Tracker\n// ============================================================================\n\n/**\n * Tracks download activity for testing concurrency and completion\n */\nexport interface DownloadTracker {\n /** Number of currently active downloads */\n activeDownloads: number;\n /** Maximum concurrent downloads observed */\n maxConcurrent: number;\n /** URLs of completed downloads */\n completedDownloads: string[];\n /** Failed downloads with error messages */\n failedDownloads: Array<{ url: string; error: string }>;\n /** Mark a download as started */\n startDownload(url: string): void;\n /** Mark a download as ended */\n endDownload(url: string, success: boolean, error?: string): void;\n /** Reset all tracking state */\n reset(): void;\n}\n\n/**\n * Create a new download tracker instance\n */\nexport function createDownloadTracker(): DownloadTracker {\n let activeDownloads = 0;\n let maxConcurrent = 0;\n const completedDownloads: string[] = [];\n const failedDownloads: Array<{ url: string; error: string }> = [];\n\n return {\n get activeDownloads() {\n return activeDownloads;\n },\n get maxConcurrent() {\n return maxConcurrent;\n },\n get completedDownloads() {\n return completedDownloads;\n },\n get failedDownloads() {\n return failedDownloads;\n },\n startDownload(url: string) {\n activeDownloads++;\n if (activeDownloads > maxConcurrent) {\n maxConcurrent = activeDownloads;\n }\n },\n endDownload(url: string, success: boolean, error?: string) {\n activeDownloads--;\n if (success) {\n completedDownloads.push(url);\n } else {\n failedDownloads.push({ url, error: error || \"Unknown error\" });\n }\n },\n reset() {\n activeDownloads = 0;\n maxConcurrent = 0;\n completedDownloads.length = 0;\n failedDownloads.length = 0;\n },\n };\n}\n\n// ============================================================================\n// URL Response Configuration\n// ============================================================================\n\n/**\n * Configuration for a mocked URL response\n */\nexport interface MockUrlResponse {\n /** HTTP status code */\n status: number;\n /** Whether the request was successful (2xx) */\n ok: boolean;\n /** Content-Type header */\n contentType?: string;\n /** Response body as base64 (for fetch_url) */\n bodyBase64?: string;\n /** Number of bytes written (for download_asset) */\n bytesWritten?: number;\n /** Actual file path where asset was saved */\n actualPath?: string;\n /** Artificial delay in milliseconds */\n delay?: number;\n}\n\n/**\n * URL response configuration for mocking HTTP requests\n */\nexport interface MockUrlConfig {\n /** Map of URL to response(s) */\n responses: Map<string, MockUrlResponse | MockUrlResponse[]>;\n /** Default response for unregistered URLs */\n defaultResponse: MockUrlResponse;\n /** Set response for a URL (can be single or array for retry testing) */\n setResponse(url: string, response: MockUrlResponse | MockUrlResponse[]): void;\n /** Get response for a URL (handles retry sequences) */\n getResponse(url: string): MockUrlResponse;\n /** Reset all URL configurations */\n reset(): void;\n}\n\n/**\n * Create a new URL config instance\n */\nexport function createMockUrlConfig(): MockUrlConfig {\n const responses = new Map<string, MockUrlResponse | MockUrlResponse[]>();\n const callCounts = new Map<string, number>();\n\n // Default: 1x1 red PNG\n const defaultResponse: MockUrlResponse = {\n status: 200,\n ok: true,\n contentType: \"image/png\",\n bodyBase64:\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==\",\n bytesWritten: 68,\n actualPath: \"assets/image.png\",\n };\n\n return {\n responses,\n defaultResponse,\n setResponse(url: string, response: MockUrlResponse | MockUrlResponse[]) {\n responses.set(url, response);\n callCounts.set(url, 0);\n },\n getResponse(url: string): MockUrlResponse {\n const config = responses.get(url);\n if (!config) return defaultResponse;\n\n if (Array.isArray(config)) {\n const count = callCounts.get(url) || 0;\n callCounts.set(url, count + 1);\n // Return the response at the current index, or the last one if exceeded\n return config[Math.min(count, config.length - 1)];\n }\n\n return config;\n },\n reset() {\n responses.clear();\n callCounts.clear();\n },\n };\n}\n\n// ============================================================================\n// Binary Execution Tracker\n// ============================================================================\n\n/**\n * Result for a mocked binary execution\n */\nexport interface MockBinaryResult {\n success: boolean;\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * Configuration for mocking binary execution\n */\nexport interface MockBinaryConfig {\n /** Map of binary commands to results */\n results: Map<string, MockBinaryResult>;\n /** Default result for unregistered binaries */\n defaultResult: MockBinaryResult;\n /** Set result for a binary command (key format: \"binaryPath args...\") */\n setResult(key: string, result: MockBinaryResult): void;\n /** Get result for a binary command */\n getResult(binaryPath: string, args: string[]): MockBinaryResult;\n /** Reset all configurations */\n reset(): void;\n}\n\n/**\n * Create a new binary config instance\n */\nexport function createMockBinaryConfig(): MockBinaryConfig {\n const results = new Map<string, MockBinaryResult>();\n\n const defaultResult: MockBinaryResult = {\n success: true,\n exitCode: 0,\n stdout: \"\",\n stderr: \"\",\n };\n\n return {\n results,\n defaultResult,\n setResult(key: string, result: MockBinaryResult) {\n results.set(key, result);\n },\n getResult(binaryPath: string, args: string[]): MockBinaryResult {\n // Try exact match first\n const exactKey = `${binaryPath} ${args.join(\" \")}`.trim();\n if (results.has(exactKey)) {\n return results.get(exactKey)!;\n }\n // Try binary name only\n if (results.has(binaryPath)) {\n return results.get(binaryPath)!;\n }\n return defaultResult;\n },\n reset() {\n results.clear();\n },\n };\n}\n\n// ============================================================================\n// Cookie Storage\n// ============================================================================\n\n/**\n * Mock cookie storage for plugin authentication testing\n */\nexport interface MockCookieStorage {\n /** Map of pluginName:projectPath to cookies */\n cookies: Map<string, Array<{ name: string; value: string; domain?: string; path?: string }>>;\n /** Get cookies for a plugin/project */\n getCookies(\n pluginName: string,\n projectPath: string\n ): Array<{ name: string; value: string; domain?: string; path?: string }>;\n /** Set cookies for a plugin/project */\n setCookies(\n pluginName: string,\n projectPath: string,\n cookies: Array<{ name: string; value: string; domain?: string; path?: string }>\n ): void;\n /** Clear all cookies */\n clear(): void;\n}\n\n/**\n * Create a new cookie storage instance\n */\nexport function createMockCookieStorage(): MockCookieStorage {\n const cookies = new Map<\n string,\n Array<{ name: string; value: string; domain?: string; path?: string }>\n >();\n\n return {\n cookies,\n getCookies(pluginName: string, projectPath: string) {\n const key = `${pluginName}:${projectPath}`;\n return cookies.get(key) || [];\n },\n setCookies(\n pluginName: string,\n projectPath: string,\n newCookies: Array<{ name: string; value: string; domain?: string; path?: string }>\n ) {\n const key = `${pluginName}:${projectPath}`;\n cookies.set(key, newCookies);\n },\n clear() {\n cookies.clear();\n },\n };\n}\n\n// ============================================================================\n// Browser Tracker\n// ============================================================================\n\n/**\n * Tracks browser open/close calls for testing\n */\nexport interface MockBrowserTracker {\n /** URLs that were opened */\n openedUrls: string[];\n /** Number of times closeBrowser was called */\n closeCount: number;\n /** Whether browser is currently open */\n isOpen: boolean;\n /** Reset tracking state */\n reset(): void;\n}\n\n/**\n * Create a new browser tracker instance\n */\nexport function createMockBrowserTracker(): MockBrowserTracker {\n const openedUrls: string[] = [];\n let closeCount = 0;\n let isOpen = false;\n\n return {\n get openedUrls() {\n return openedUrls;\n },\n get closeCount() {\n return closeCount;\n },\n set closeCount(value: number) {\n closeCount = value;\n },\n get isOpen() {\n return isOpen;\n },\n set isOpen(value: boolean) {\n isOpen = value;\n },\n reset() {\n openedUrls.length = 0;\n closeCount = 0;\n isOpen = false;\n },\n };\n}\n\n// ============================================================================\n// Dialog Tracker\n// ============================================================================\n\n/**\n * Dialog result types matching the moss backend\n */\nexport interface MockDialogResult {\n type: \"submitted\" | \"cancelled\";\n value?: unknown;\n}\n\n/**\n * Tracks dialog interactions for testing\n */\nexport interface MockDialogTracker {\n /** Dialogs that were shown (with their URLs and titles) */\n shownDialogs: Array<{ url: string; title: string; width: number; height: number }>;\n /** Configure the next dialog result (for automatic response) */\n nextResult: MockDialogResult | null;\n /** Submitted results by dialog ID */\n submittedResults: Map<string, MockDialogResult>;\n /** Set the result for the next dialog shown */\n setNextResult(result: MockDialogResult): void;\n /** Simulate user submitting a dialog result */\n submitResult(dialogId: string, value: unknown): void;\n /** Simulate user cancelling a dialog */\n cancelDialog(dialogId: string): void;\n /** Reset tracking state */\n reset(): void;\n}\n\n/**\n * Create a new dialog tracker instance\n */\nexport function createMockDialogTracker(): MockDialogTracker {\n const shownDialogs: Array<{ url: string; title: string; width: number; height: number }> = [];\n const submittedResults = new Map<string, MockDialogResult>();\n let nextResult: MockDialogResult | null = null;\n\n return {\n get shownDialogs() {\n return shownDialogs;\n },\n get nextResult() {\n return nextResult;\n },\n set nextResult(value: MockDialogResult | null) {\n nextResult = value;\n },\n get submittedResults() {\n return submittedResults;\n },\n setNextResult(result: MockDialogResult) {\n nextResult = result;\n },\n submitResult(dialogId: string, value: unknown) {\n submittedResults.set(dialogId, { type: \"submitted\", value });\n },\n cancelDialog(dialogId: string) {\n submittedResults.set(dialogId, { type: \"cancelled\" });\n },\n reset() {\n shownDialogs.length = 0;\n submittedResults.clear();\n nextResult = null;\n },\n };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Extract filename from URL (mimics Rust backend behavior)\n */\nfunction extractFilenameFromUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const segments = pathname.split(\"/\").filter((s) => s.length > 0);\n\n // Try to find UUID in path\n for (const segment of segments) {\n if (\n /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(\n segment\n )\n ) {\n return `${segment}.png`; // Default to PNG for mock\n }\n }\n\n // Fallback to last segment or hash\n const lastSegment = segments[segments.length - 1] || \"image\";\n return lastSegment.includes(\".\") ? lastSegment : `${lastSegment}.png`;\n } catch {\n return \"image.png\";\n }\n}\n\n// ============================================================================\n// Main Setup Function\n// ============================================================================\n\n/**\n * Options for setting up mock Tauri environment\n */\nexport interface SetupMockTauriOptions {\n /** Plugin name for internal context (default: \"test-plugin\") */\n pluginName?: string;\n /** Project path for internal context (default: \"/test/project\") */\n projectPath?: string;\n}\n\n/**\n * Context returned by setupMockTauri with all mock utilities\n */\nexport interface MockTauriContext {\n /** In-memory filesystem */\n filesystem: MockFilesystem;\n /** Download tracking for concurrency tests */\n downloadTracker: DownloadTracker;\n /** URL response configuration */\n urlConfig: MockUrlConfig;\n /** Binary execution configuration */\n binaryConfig: MockBinaryConfig;\n /** Cookie storage */\n cookieStorage: MockCookieStorage;\n /** Browser open/close tracking */\n browserTracker: MockBrowserTracker;\n /** Dialog interaction tracking */\n dialogTracker: MockDialogTracker;\n /** The project path used for internal context */\n projectPath: string;\n /** The plugin name used for internal context */\n pluginName: string;\n /** Cleanup function - must be called after tests */\n cleanup: () => void;\n}\n\n/**\n * Set up mock Tauri IPC for testing\n *\n * This sets up `window.__TAURI__.core.invoke` to intercept all IPC calls\n * and route them to in-memory implementations. It also sets up\n * `__MOSS_INTERNAL_CONTEXT__` for the context-aware APIs.\n *\n * @param options - Optional configuration for project path and plugin name\n * @returns Context with mock utilities and cleanup function\n *\n * @example\n * ```typescript\n * const ctx = setupMockTauri({ projectPath: \"/my/project\", pluginName: \"my-plugin\" });\n *\n * // Set up test data\n * ctx.filesystem.setFile(\"/my/project/article.md\", \"# Test\");\n * ctx.urlConfig.setResponse(\"https://example.com/image.png\", {\n * status: 200,\n * ok: true,\n * contentType: \"image/png\",\n * bytesWritten: 1024,\n * });\n *\n * // Run your plugin code...\n *\n * // Verify results\n * expect(ctx.downloadTracker.completedDownloads).toHaveLength(1);\n *\n * // Cleanup\n * ctx.cleanup();\n * ```\n */\nexport function setupMockTauri(options?: SetupMockTauriOptions): MockTauriContext {\n const projectPath = options?.projectPath ?? \"/test/project\";\n const pluginName = options?.pluginName ?? \"test-plugin\";\n\n const filesystem = createMockFilesystem();\n const downloadTracker = createDownloadTracker();\n const urlConfig = createMockUrlConfig();\n const binaryConfig = createMockBinaryConfig();\n const cookieStorage = createMockCookieStorage();\n const browserTracker = createMockBrowserTracker();\n const dialogTracker = createMockDialogTracker();\n\n // Create invoke handler\n const invoke = async (cmd: string, args?: InvokeArgs): Promise<unknown> => {\n const payload = args as Record<string, unknown> | undefined;\n\n switch (cmd) {\n // ======================================================================\n // Filesystem Operations\n // ======================================================================\n case \"read_project_file\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const fullPath = `${projectPath}/${relativePath}`;\n const file = filesystem.getFile(fullPath);\n if (file) {\n return file.content;\n }\n throw new Error(`File not found: ${fullPath}`);\n }\n\n case \"write_project_file\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const content = payload?.data as string; // Note: moss-api uses 'data' not 'content'\n const fullPath = `${projectPath}/${relativePath}`;\n filesystem.setFile(fullPath, content);\n return null;\n }\n\n case \"list_project_files\": {\n const projectPath = payload?.projectPath as string;\n // Return all file paths relative to the project path\n const allPaths = filesystem.listFiles();\n return allPaths\n .filter((p) => p.startsWith(projectPath + \"/\"))\n .map((p) => p.substring(projectPath.length + 1));\n }\n\n case \"project_file_exists\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const fullPath = `${projectPath}/${relativePath}`;\n return filesystem.getFile(fullPath) !== undefined;\n }\n\n // ======================================================================\n // Plugin Storage Operations\n // ======================================================================\n case \"read_plugin_file\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const rp = payload?.relativePath as string;\n const fullPath = `${pp}/.moss/plugins/${pn}/${rp}`;\n const file = filesystem.getFile(fullPath);\n if (file) {\n return file.content;\n }\n throw new Error(`Plugin file not found: ${fullPath}`);\n }\n\n case \"write_plugin_file\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const rp = payload?.relativePath as string;\n const content = payload?.content as string;\n const fullPath = `${pp}/.moss/plugins/${pn}/${rp}`;\n filesystem.setFile(fullPath, content);\n return null;\n }\n\n case \"list_plugin_files\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const prefix = `${pp}/.moss/plugins/${pn}/`;\n const allPaths = filesystem.listFiles();\n return allPaths\n .filter((p) => p.startsWith(prefix))\n .map((p) => p.substring(prefix.length));\n }\n\n case \"plugin_file_exists\": {\n const pn = payload?.pluginName as string;\n const pp = payload?.projectPath as string;\n const rp = payload?.relativePath as string;\n const fullPath = `${pp}/.moss/plugins/${pn}/${rp}`;\n return filesystem.getFile(fullPath) !== undefined;\n }\n\n // ======================================================================\n // HTTP Operations\n // ======================================================================\n case \"fetch_url\": {\n const url = payload?.url as string;\n const response = urlConfig.getResponse(url);\n\n if (response.delay) {\n return new Promise((resolve) =>\n setTimeout(\n () =>\n resolve({\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n }),\n response.delay\n )\n );\n }\n\n return {\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n };\n }\n\n case \"http_post\": {\n // HTTP POST - similar to fetch_url but for POST requests\n const url = payload?.url as string;\n const response = urlConfig.getResponse(url);\n\n if (response.delay) {\n return new Promise((resolve) =>\n setTimeout(\n () =>\n resolve({\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n }),\n response.delay\n )\n );\n }\n\n return {\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n };\n }\n\n case \"download_asset\": {\n const url = payload?.url as string;\n const targetDir = payload?.targetDir as string;\n const response = urlConfig.getResponse(url);\n\n downloadTracker.startDownload(url);\n\n // status 0 simulates a network error/timeout - throw an error\n if (response.status === 0) {\n downloadTracker.endDownload(url, false, \"Network error\");\n throw new Error(\"Network timeout\");\n }\n\n // Generate actual_path based on URL or use configured value\n const actualPath =\n response.actualPath || `${targetDir}/${extractFilenameFromUrl(url)}`;\n\n const result = {\n status: response.status,\n ok: response.ok,\n content_type: response.contentType || null,\n bytes_written: response.bytesWritten || 0,\n actual_path: actualPath,\n };\n\n if (response.delay) {\n return new Promise((resolve) =>\n setTimeout(() => {\n downloadTracker.endDownload(url, response.ok);\n resolve(result);\n }, response.delay)\n );\n }\n\n downloadTracker.endDownload(url, response.ok);\n return result;\n }\n\n // ======================================================================\n // Cookie Operations\n // ======================================================================\n case \"get_plugin_cookie\": {\n const pluginName = payload?.pluginName as string;\n const projectPath = payload?.projectPath as string;\n return cookieStorage.getCookies(pluginName, projectPath);\n }\n\n case \"set_plugin_cookie\": {\n const pluginName = payload?.pluginName as string;\n const projectPath = payload?.projectPath as string;\n const cookies = payload?.cookies as Array<{\n name: string;\n value: string;\n domain?: string;\n path?: string;\n }>;\n cookieStorage.setCookies(pluginName, projectPath, cookies);\n return null;\n }\n\n // ======================================================================\n // Browser Operations\n // ======================================================================\n case \"open_plugin_browser\": {\n const url = payload?.url as string;\n browserTracker.openedUrls.push(url);\n (browserTracker as { isOpen: boolean }).isOpen = true;\n return null;\n }\n\n case \"close_plugin_browser\": {\n (browserTracker as { closeCount: number }).closeCount++;\n (browserTracker as { isOpen: boolean }).isOpen = false;\n return null;\n }\n\n // ======================================================================\n // Dialog Operations\n // ======================================================================\n case \"show_plugin_dialog\": {\n const url = payload?.url as string;\n const title = payload?.title as string;\n const width = (payload?.width as number) ?? 500;\n const height = (payload?.height as number) ?? 400;\n\n // Track the dialog\n (dialogTracker.shownDialogs as Array<{ url: string; title: string; width: number; height: number }>).push({\n url,\n title,\n width,\n height,\n });\n\n // If a next result is configured, return it immediately\n if (dialogTracker.nextResult) {\n const result = dialogTracker.nextResult;\n (dialogTracker as { nextResult: MockDialogResult | null }).nextResult = null;\n return result;\n }\n\n // Default to cancelled\n return { type: \"cancelled\" };\n }\n\n case \"submit_dialog_result\": {\n const dialogId = payload?.dialogId as string;\n const result = payload?.result as MockDialogResult;\n\n if (result) {\n dialogTracker.submittedResults.set(dialogId, result);\n }\n\n return true;\n }\n\n // ======================================================================\n // Binary Execution\n // ======================================================================\n case \"execute_binary\": {\n const binaryPath = payload?.binaryPath as string;\n const binaryArgs = payload?.args as string[];\n const result = binaryConfig.getResult(binaryPath, binaryArgs);\n\n return {\n success: result.success,\n exit_code: result.exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n };\n }\n\n // ======================================================================\n // Messaging (silent no-op)\n // ======================================================================\n case \"plugin_message\": {\n // Silently accept plugin messages (logs, progress, errors, etc.)\n return null;\n }\n\n default:\n console.warn(`Unhandled IPC command: ${cmd}`);\n return null;\n }\n };\n\n // Set up window.__TAURI__ directly (moss-api checks for this)\n const w = globalThis as unknown as {\n window?: {\n __TAURI__?: { core?: { invoke: typeof invoke } };\n __MOSS_INTERNAL_CONTEXT__?: {\n plugin_name: string;\n project_path: string;\n moss_dir: string;\n };\n };\n };\n\n // Ensure window exists (for Node.js environments like happy-dom)\n if (typeof w.window === \"undefined\") {\n (globalThis as unknown as { window: object }).window = {};\n }\n\n const win = (globalThis as unknown as {\n window: {\n __TAURI__?: { core?: { invoke: typeof invoke } };\n __MOSS_INTERNAL_CONTEXT__?: {\n plugin_name: string;\n project_path: string;\n moss_dir: string;\n };\n };\n }).window;\n\n win.__TAURI__ = {\n core: { invoke },\n };\n\n // Set up internal context for context-aware APIs\n win.__MOSS_INTERNAL_CONTEXT__ = {\n plugin_name: pluginName,\n project_path: projectPath,\n moss_dir: `${projectPath}/.moss`,\n };\n\n return {\n filesystem,\n downloadTracker,\n urlConfig,\n binaryConfig,\n cookieStorage,\n browserTracker,\n dialogTracker,\n projectPath,\n pluginName,\n cleanup: () => {\n // Clear the mock Tauri interface and internal context\n delete win.__TAURI__;\n delete win.__MOSS_INTERNAL_CONTEXT__;\n filesystem.clear();\n downloadTracker.reset();\n urlConfig.reset();\n binaryConfig.reset();\n cookieStorage.clear();\n browserTracker.reset();\n dialogTracker.reset();\n },\n };\n}\n"],"mappings":"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"}
|