@symbiosis-lab/moss-api 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/index.d.mts +176 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +190 -1
- package/dist/index.mjs.map +1 -1
- package/dist/testing/index.d.mts +33 -0
- package/dist/testing/index.d.mts.map +1 -1
- package/dist/testing/index.mjs +73 -1
- package/dist/testing/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/Symbiosis-Lab/moss-api/actions/workflows/ci.yml)
|
|
4
4
|
[](https://codecov.io/gh/Symbiosis-Lab/moss-api)
|
|
5
|
-
[](https://www.npmjs.com/package/@symbiosis-lab/moss-api)
|
|
6
6
|
|
|
7
7
|
Official API for building Moss plugins. Provides types and utilities for plugin development.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install moss-api
|
|
12
|
+
npm install @symbiosis-lab/moss-api
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
HookResult,
|
|
24
24
|
PluginManifest,
|
|
25
25
|
PluginCategory,
|
|
26
|
-
} from "moss-api";
|
|
26
|
+
} from "@symbiosis-lab/moss-api";
|
|
27
27
|
|
|
28
28
|
// Define your plugin manifest
|
|
29
29
|
const manifest: PluginManifest = {
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
log,
|
|
52
52
|
warn,
|
|
53
53
|
error,
|
|
54
|
-
} from "moss-api";
|
|
54
|
+
} from "@symbiosis-lab/moss-api";
|
|
55
55
|
|
|
56
56
|
// Set plugin context (call once at plugin initialization)
|
|
57
57
|
setMessageContext("my-plugin", "on_deploy");
|
|
@@ -74,7 +74,7 @@ await reportComplete({ url: "https://example.com" });
|
|
|
74
74
|
### Browser Utilities
|
|
75
75
|
|
|
76
76
|
```typescript
|
|
77
|
-
import { openBrowser, closeBrowser } from "moss-api";
|
|
77
|
+
import { openBrowser, closeBrowser } from "@symbiosis-lab/moss-api";
|
|
78
78
|
|
|
79
79
|
// Open authentication page in plugin browser window
|
|
80
80
|
await openBrowser("https://example.com/auth");
|
|
@@ -86,7 +86,7 @@ await closeBrowser();
|
|
|
86
86
|
### Tauri Utilities
|
|
87
87
|
|
|
88
88
|
```typescript
|
|
89
|
-
import { getTauriCore, isTauriAvailable } from "moss-api";
|
|
89
|
+
import { getTauriCore, isTauriAvailable } from "@symbiosis-lab/moss-api";
|
|
90
90
|
|
|
91
91
|
// Check if running in Tauri environment
|
|
92
92
|
if (isTauriAvailable()) {
|
package/dist/index.d.mts
CHANGED
|
@@ -86,7 +86,24 @@ interface DeploymentInfo {
|
|
|
86
86
|
url: string;
|
|
87
87
|
deployed_at: string;
|
|
88
88
|
metadata: Record<string, string>;
|
|
89
|
+
/** DNS target for custom domain configuration */
|
|
90
|
+
dns_target?: DnsTarget;
|
|
89
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* DNS target type for custom domain configuration
|
|
94
|
+
* Provided by deploy plugins to configure DNS records
|
|
95
|
+
*/
|
|
96
|
+
type DnsTarget = {
|
|
97
|
+
type: "cname";
|
|
98
|
+
hostname: string;
|
|
99
|
+
} | {
|
|
100
|
+
type: "a";
|
|
101
|
+
ips: string[];
|
|
102
|
+
} | {
|
|
103
|
+
type: "github-pages";
|
|
104
|
+
a_records: string[];
|
|
105
|
+
cname_target: string;
|
|
106
|
+
};
|
|
90
107
|
//#endregion
|
|
91
108
|
//#region src/types/hooks.d.ts
|
|
92
109
|
/**
|
|
@@ -621,5 +638,163 @@ declare function getPluginCookie(): Promise<Cookie[]>;
|
|
|
621
638
|
*/
|
|
622
639
|
declare function setPluginCookie(cookies: Cookie[]): Promise<void>;
|
|
623
640
|
//#endregion
|
|
624
|
-
|
|
641
|
+
//#region src/utils/window.d.ts
|
|
642
|
+
/**
|
|
643
|
+
* Window utilities for plugins
|
|
644
|
+
* Enables plugins to show custom dialogs and UI elements
|
|
645
|
+
*/
|
|
646
|
+
/**
|
|
647
|
+
* Result from a dialog interaction
|
|
648
|
+
*/
|
|
649
|
+
interface DialogResult {
|
|
650
|
+
type: "submitted" | "cancelled";
|
|
651
|
+
value?: unknown;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Options for showing a plugin dialog
|
|
655
|
+
*/
|
|
656
|
+
interface ShowDialogOptions {
|
|
657
|
+
/** URL to load in the dialog (can be data: URL with embedded HTML) */
|
|
658
|
+
url: string;
|
|
659
|
+
/** Dialog window title */
|
|
660
|
+
title: string;
|
|
661
|
+
/** Dialog width in pixels */
|
|
662
|
+
width?: number;
|
|
663
|
+
/** Dialog height in pixels */
|
|
664
|
+
height?: number;
|
|
665
|
+
/** Maximum time to wait for user response in milliseconds */
|
|
666
|
+
timeoutMs?: number;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Show a plugin dialog and wait for user response
|
|
670
|
+
*
|
|
671
|
+
* The dialog can be an embedded HTML page (via data: URL) that communicates
|
|
672
|
+
* back to the plugin via the submitDialogResult function.
|
|
673
|
+
*
|
|
674
|
+
* @param options - Dialog configuration
|
|
675
|
+
* @returns Dialog result with submitted value or cancellation
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```typescript
|
|
679
|
+
* const result = await showPluginDialog({
|
|
680
|
+
* url: createMyDialogUrl(),
|
|
681
|
+
* title: "Create Repository",
|
|
682
|
+
* width: 400,
|
|
683
|
+
* height: 300,
|
|
684
|
+
* });
|
|
685
|
+
*
|
|
686
|
+
* if (result.type === "submitted") {
|
|
687
|
+
* console.log("User submitted:", result.value);
|
|
688
|
+
* } else {
|
|
689
|
+
* console.log("User cancelled");
|
|
690
|
+
* }
|
|
691
|
+
* ```
|
|
692
|
+
*/
|
|
693
|
+
declare function showPluginDialog(options: ShowDialogOptions): Promise<DialogResult>;
|
|
694
|
+
/**
|
|
695
|
+
* Submit a result from within a plugin dialog
|
|
696
|
+
*
|
|
697
|
+
* This is called from inside the dialog HTML to send data back to the plugin.
|
|
698
|
+
* The dialog will be closed automatically after submission.
|
|
699
|
+
*
|
|
700
|
+
* @param dialogId - The dialog ID (provided in the dialog's query string)
|
|
701
|
+
* @param value - The value to submit
|
|
702
|
+
* @returns Whether the submission was successful
|
|
703
|
+
*
|
|
704
|
+
* @example
|
|
705
|
+
* ```typescript
|
|
706
|
+
* // Inside dialog HTML:
|
|
707
|
+
* const dialogId = new URLSearchParams(location.search).get('dialogId');
|
|
708
|
+
* await submitDialogResult(dialogId, { repoName: 'my-repo' });
|
|
709
|
+
* ```
|
|
710
|
+
*/
|
|
711
|
+
declare function submitDialogResult(dialogId: string, value: unknown): Promise<boolean>;
|
|
712
|
+
/**
|
|
713
|
+
* Cancel a plugin dialog
|
|
714
|
+
*
|
|
715
|
+
* This is called from inside the dialog HTML to cancel without submitting.
|
|
716
|
+
* The dialog will be closed automatically.
|
|
717
|
+
*
|
|
718
|
+
* @param dialogId - The dialog ID (provided in the dialog's query string)
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```typescript
|
|
722
|
+
* // Inside dialog HTML:
|
|
723
|
+
* const dialogId = new URLSearchParams(location.search).get('dialogId');
|
|
724
|
+
* await cancelDialog(dialogId);
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
declare function cancelDialog(dialogId: string): Promise<boolean>;
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region src/utils/events.d.ts
|
|
730
|
+
/**
|
|
731
|
+
* Event utilities for plugin communication
|
|
732
|
+
*
|
|
733
|
+
* Provides a way for plugins and dialog windows to communicate
|
|
734
|
+
* via Tauri's event system.
|
|
735
|
+
*/
|
|
736
|
+
/**
|
|
737
|
+
* Check if Tauri event API is available
|
|
738
|
+
*/
|
|
739
|
+
declare function isEventApiAvailable(): boolean;
|
|
740
|
+
/**
|
|
741
|
+
* Emit an event to other parts of the application
|
|
742
|
+
*
|
|
743
|
+
* @param event - Event name (e.g., "repo-created", "dialog-result")
|
|
744
|
+
* @param payload - Data to send with the event
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```typescript
|
|
748
|
+
* // From dialog:
|
|
749
|
+
* await emitEvent("repo-name-validated", { name: "my-repo", available: true });
|
|
750
|
+
*
|
|
751
|
+
* // From plugin:
|
|
752
|
+
* await emitEvent("deployment-started", { url: "https://github.com/..." });
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
755
|
+
declare function emitEvent(event: string, payload?: unknown): Promise<void>;
|
|
756
|
+
/**
|
|
757
|
+
* Listen for events from other parts of the application
|
|
758
|
+
*
|
|
759
|
+
* @param event - Event name to listen for
|
|
760
|
+
* @param handler - Function to call when event is received
|
|
761
|
+
* @returns Cleanup function to stop listening
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```typescript
|
|
765
|
+
* const unlisten = await onEvent<{ name: string; available: boolean }>(
|
|
766
|
+
* "repo-name-validated",
|
|
767
|
+
* (data) => {
|
|
768
|
+
* console.log(`Repo ${data.name} is ${data.available ? "available" : "taken"}`);
|
|
769
|
+
* }
|
|
770
|
+
* );
|
|
771
|
+
*
|
|
772
|
+
* // Later, to stop listening:
|
|
773
|
+
* unlisten();
|
|
774
|
+
* ```
|
|
775
|
+
*/
|
|
776
|
+
declare function onEvent<T>(event: string, handler: (payload: T) => void): Promise<() => void>;
|
|
777
|
+
/**
|
|
778
|
+
* Wait for a single event occurrence
|
|
779
|
+
*
|
|
780
|
+
* @param event - Event name to wait for
|
|
781
|
+
* @param timeoutMs - Maximum time to wait (default: 30000ms)
|
|
782
|
+
* @returns Promise that resolves with the event payload
|
|
783
|
+
* @throws Error if timeout is reached
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* ```typescript
|
|
787
|
+
* try {
|
|
788
|
+
* const result = await waitForEvent<{ confirmed: boolean }>("user-confirmed", 10000);
|
|
789
|
+
* if (result.confirmed) {
|
|
790
|
+
* // proceed
|
|
791
|
+
* }
|
|
792
|
+
* } catch (e) {
|
|
793
|
+
* console.log("User did not respond in time");
|
|
794
|
+
* }
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
declare function waitForEvent<T>(event: string, timeoutMs?: number): Promise<T>;
|
|
798
|
+
//#endregion
|
|
799
|
+
export { AfterDeployContext, ArticleInfo, BaseContext, BeforeBuildContext, CompleteMessage, Cookie, DeploymentInfo, DialogResult, DnsTarget, DownloadOptions, DownloadResult, ErrorMessage, ExecuteOptions, ExecuteResult, FetchOptions, FetchResult, HookResult, LogMessage, OnBuildContext, OnDeployContext, PluginCategory, PluginManifest, PluginMessage, ProgressMessage, ProjectInfo, ShowDialogOptions, SourceFiles, TauriCore, cancelDialog, closeBrowser, downloadAsset, emitEvent, error, executeBinary, fetchUrl, fileExists, getMessageContext, getPluginCookie, getTauriCore, isEventApiAvailable, isTauriAvailable, listFiles, listPluginFiles, log, onEvent, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, sendMessage, setMessageContext, setPluginCookie, showPluginDialog, submitDialogResult, waitForEvent, warn, writeFile, writePluginFile };
|
|
625
800
|
//# 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/cookies.ts"],"sourcesContent":[],"mappings":";;AAIA;AAOA;AAWY,UAlBK,WAAA,CAkBS;;;;ECJT,aAAA,CAAW,EAAA,MAAA;AAQ5B;AAKiB,UDpBA,cAAA,CCoBe;EAOf,IAAA,EAAA,MAAA;EAOA,OAAA,EAAA,MAAA;EAEL,KAAA,EAAA,MAAA;EACG,QAAA,EDjCH,cCiCG;EAH6B,WAAA,CAAA,EAAA,MAAA;EAAW,IAAA,CAAA,EAAA,MAAA;EAStC,MAAA,CAAA,EAAA,MAAW;EAUX,MAAA,CAAA,ED7CN,MC6CiB,CAAA,MAAA,EAAA,OAIb,CAAA;AASf;
|
|
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/cookies.ts","../src/utils/window.ts","../src/utils/events.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -608,5 +608,194 @@ async function setPluginCookie(cookies) {
|
|
|
608
608
|
}
|
|
609
609
|
|
|
610
610
|
//#endregion
|
|
611
|
-
|
|
611
|
+
//#region src/utils/window.ts
|
|
612
|
+
/**
|
|
613
|
+
* Window utilities for plugins
|
|
614
|
+
* Enables plugins to show custom dialogs and UI elements
|
|
615
|
+
*/
|
|
616
|
+
/**
|
|
617
|
+
* Show a plugin dialog and wait for user response
|
|
618
|
+
*
|
|
619
|
+
* The dialog can be an embedded HTML page (via data: URL) that communicates
|
|
620
|
+
* back to the plugin via the submitDialogResult function.
|
|
621
|
+
*
|
|
622
|
+
* @param options - Dialog configuration
|
|
623
|
+
* @returns Dialog result with submitted value or cancellation
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* const result = await showPluginDialog({
|
|
628
|
+
* url: createMyDialogUrl(),
|
|
629
|
+
* title: "Create Repository",
|
|
630
|
+
* width: 400,
|
|
631
|
+
* height: 300,
|
|
632
|
+
* });
|
|
633
|
+
*
|
|
634
|
+
* if (result.type === "submitted") {
|
|
635
|
+
* console.log("User submitted:", result.value);
|
|
636
|
+
* } else {
|
|
637
|
+
* console.log("User cancelled");
|
|
638
|
+
* }
|
|
639
|
+
* ```
|
|
640
|
+
*/
|
|
641
|
+
async function showPluginDialog(options) {
|
|
642
|
+
return await getTauriCore().invoke("show_plugin_dialog", {
|
|
643
|
+
url: options.url,
|
|
644
|
+
title: options.title,
|
|
645
|
+
width: options.width ?? 500,
|
|
646
|
+
height: options.height ?? 400,
|
|
647
|
+
timeoutMs: options.timeoutMs ?? 3e5
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Submit a result from within a plugin dialog
|
|
652
|
+
*
|
|
653
|
+
* This is called from inside the dialog HTML to send data back to the plugin.
|
|
654
|
+
* The dialog will be closed automatically after submission.
|
|
655
|
+
*
|
|
656
|
+
* @param dialogId - The dialog ID (provided in the dialog's query string)
|
|
657
|
+
* @param value - The value to submit
|
|
658
|
+
* @returns Whether the submission was successful
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```typescript
|
|
662
|
+
* // Inside dialog HTML:
|
|
663
|
+
* const dialogId = new URLSearchParams(location.search).get('dialogId');
|
|
664
|
+
* await submitDialogResult(dialogId, { repoName: 'my-repo' });
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
async function submitDialogResult(dialogId, value) {
|
|
668
|
+
return getTauriCore().invoke("submit_dialog_result", {
|
|
669
|
+
dialogId,
|
|
670
|
+
result: {
|
|
671
|
+
type: "submitted",
|
|
672
|
+
value
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Cancel a plugin dialog
|
|
678
|
+
*
|
|
679
|
+
* This is called from inside the dialog HTML to cancel without submitting.
|
|
680
|
+
* The dialog will be closed automatically.
|
|
681
|
+
*
|
|
682
|
+
* @param dialogId - The dialog ID (provided in the dialog's query string)
|
|
683
|
+
*
|
|
684
|
+
* @example
|
|
685
|
+
* ```typescript
|
|
686
|
+
* // Inside dialog HTML:
|
|
687
|
+
* const dialogId = new URLSearchParams(location.search).get('dialogId');
|
|
688
|
+
* await cancelDialog(dialogId);
|
|
689
|
+
* ```
|
|
690
|
+
*/
|
|
691
|
+
async function cancelDialog(dialogId) {
|
|
692
|
+
return getTauriCore().invoke("submit_dialog_result", {
|
|
693
|
+
dialogId,
|
|
694
|
+
result: { type: "cancelled" }
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
//#endregion
|
|
699
|
+
//#region src/utils/events.ts
|
|
700
|
+
/**
|
|
701
|
+
* Get Tauri event API
|
|
702
|
+
* @internal
|
|
703
|
+
*/
|
|
704
|
+
function getTauriEvent() {
|
|
705
|
+
const w = window;
|
|
706
|
+
if (!w.__TAURI__?.event) throw new Error("Tauri event API not available");
|
|
707
|
+
return w.__TAURI__.event;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Check if Tauri event API is available
|
|
711
|
+
*/
|
|
712
|
+
function isEventApiAvailable() {
|
|
713
|
+
return !!window.__TAURI__?.event;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Emit an event to other parts of the application
|
|
717
|
+
*
|
|
718
|
+
* @param event - Event name (e.g., "repo-created", "dialog-result")
|
|
719
|
+
* @param payload - Data to send with the event
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```typescript
|
|
723
|
+
* // From dialog:
|
|
724
|
+
* await emitEvent("repo-name-validated", { name: "my-repo", available: true });
|
|
725
|
+
*
|
|
726
|
+
* // From plugin:
|
|
727
|
+
* await emitEvent("deployment-started", { url: "https://github.com/..." });
|
|
728
|
+
* ```
|
|
729
|
+
*/
|
|
730
|
+
async function emitEvent(event, payload) {
|
|
731
|
+
await getTauriEvent().emit(event, payload);
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Listen for events from other parts of the application
|
|
735
|
+
*
|
|
736
|
+
* @param event - Event name to listen for
|
|
737
|
+
* @param handler - Function to call when event is received
|
|
738
|
+
* @returns Cleanup function to stop listening
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```typescript
|
|
742
|
+
* const unlisten = await onEvent<{ name: string; available: boolean }>(
|
|
743
|
+
* "repo-name-validated",
|
|
744
|
+
* (data) => {
|
|
745
|
+
* console.log(`Repo ${data.name} is ${data.available ? "available" : "taken"}`);
|
|
746
|
+
* }
|
|
747
|
+
* );
|
|
748
|
+
*
|
|
749
|
+
* // Later, to stop listening:
|
|
750
|
+
* unlisten();
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
async function onEvent(event, handler) {
|
|
754
|
+
return await getTauriEvent().listen(event, (e) => {
|
|
755
|
+
handler(e.payload);
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Wait for a single event occurrence
|
|
760
|
+
*
|
|
761
|
+
* @param event - Event name to wait for
|
|
762
|
+
* @param timeoutMs - Maximum time to wait (default: 30000ms)
|
|
763
|
+
* @returns Promise that resolves with the event payload
|
|
764
|
+
* @throws Error if timeout is reached
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* ```typescript
|
|
768
|
+
* try {
|
|
769
|
+
* const result = await waitForEvent<{ confirmed: boolean }>("user-confirmed", 10000);
|
|
770
|
+
* if (result.confirmed) {
|
|
771
|
+
* // proceed
|
|
772
|
+
* }
|
|
773
|
+
* } catch (e) {
|
|
774
|
+
* console.log("User did not respond in time");
|
|
775
|
+
* }
|
|
776
|
+
* ```
|
|
777
|
+
*/
|
|
778
|
+
async function waitForEvent(event, timeoutMs = 3e4) {
|
|
779
|
+
return new Promise((resolve, reject) => {
|
|
780
|
+
let unlisten = null;
|
|
781
|
+
let timeoutId;
|
|
782
|
+
const cleanup = () => {
|
|
783
|
+
if (unlisten) unlisten();
|
|
784
|
+
clearTimeout(timeoutId);
|
|
785
|
+
};
|
|
786
|
+
timeoutId = setTimeout(() => {
|
|
787
|
+
cleanup();
|
|
788
|
+
reject(/* @__PURE__ */ new Error(`Timeout waiting for event: ${event}`));
|
|
789
|
+
}, timeoutMs);
|
|
790
|
+
onEvent(event, (payload) => {
|
|
791
|
+
cleanup();
|
|
792
|
+
resolve(payload);
|
|
793
|
+
}).then((unlistenFn) => {
|
|
794
|
+
unlisten = unlistenFn;
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
//#endregion
|
|
800
|
+
export { cancelDialog, closeBrowser, downloadAsset, emitEvent, error, executeBinary, fetchUrl, fileExists, getMessageContext, getPluginCookie, getTauriCore, isEventApiAvailable, isTauriAvailable, listFiles, listPluginFiles, log, onEvent, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, sendMessage, setMessageContext, setPluginCookie, showPluginDialog, submitDialogResult, waitForEvent, warn, writeFile, writePluginFile };
|
|
612
801
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"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/cookies.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 * 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["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/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 * 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.d.mts
CHANGED
|
@@ -198,6 +198,37 @@ interface MockBrowserTracker {
|
|
|
198
198
|
* Create a new browser tracker instance
|
|
199
199
|
*/
|
|
200
200
|
declare function createMockBrowserTracker(): MockBrowserTracker;
|
|
201
|
+
/**
|
|
202
|
+
* Dialog result types matching the moss backend
|
|
203
|
+
*/
|
|
204
|
+
interface MockDialogResult {
|
|
205
|
+
type: "submitted" | "cancelled";
|
|
206
|
+
value?: unknown;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Tracks dialog interactions for testing
|
|
210
|
+
*/
|
|
211
|
+
interface MockDialogTracker {
|
|
212
|
+
/** Dialogs that were shown (with their URLs and titles) */
|
|
213
|
+
shownDialogs: Array<{
|
|
214
|
+
url: string;
|
|
215
|
+
title: string;
|
|
216
|
+
width: number;
|
|
217
|
+
height: number;
|
|
218
|
+
}>;
|
|
219
|
+
/** Configure the next dialog result (for automatic response) */
|
|
220
|
+
nextResult: MockDialogResult | null;
|
|
221
|
+
/** Submitted results by dialog ID */
|
|
222
|
+
submittedResults: Map<string, MockDialogResult>;
|
|
223
|
+
/** Set the result for the next dialog shown */
|
|
224
|
+
setNextResult(result: MockDialogResult): void;
|
|
225
|
+
/** Simulate user submitting a dialog result */
|
|
226
|
+
submitResult(dialogId: string, value: unknown): void;
|
|
227
|
+
/** Simulate user cancelling a dialog */
|
|
228
|
+
cancelDialog(dialogId: string): void;
|
|
229
|
+
/** Reset tracking state */
|
|
230
|
+
reset(): void;
|
|
231
|
+
}
|
|
201
232
|
/**
|
|
202
233
|
* Options for setting up mock Tauri environment
|
|
203
234
|
*/
|
|
@@ -223,6 +254,8 @@ interface MockTauriContext {
|
|
|
223
254
|
cookieStorage: MockCookieStorage;
|
|
224
255
|
/** Browser open/close tracking */
|
|
225
256
|
browserTracker: MockBrowserTracker;
|
|
257
|
+
/** Dialog interaction tracking */
|
|
258
|
+
dialogTracker: MockDialogTracker;
|
|
226
259
|
/** The project path used for internal context */
|
|
227
260
|
projectPath: string;
|
|
228
261
|
/** The plugin name used for internal context */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/testing/mock-tauri.ts"],"sourcesContent":[],"mappings":";;AA0CA;AASA;;;;;AAkBA;AA0CA;AAoBA;AAiDA;AAoBA;;;;;;;;;AAgBA;AAiDA;AAUA;;;;;;;AAgBA;AAyCA;;AAEW,UApSM,QAAA,CAoSN;EAKN,OAAA,EAAA,MAAA;EAKQ,SAAA,EA5SA,IA4SA;EAAK,UAAA,EA3SJ,IA2SI;AASlB;AAiCA;AAcA;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/testing/mock-tauri.ts"],"sourcesContent":[],"mappings":";;AA0CA;AASA;;;;;AAkBA;AA0CA;AAoBA;AAiDA;AAoBA;;;;;;;;;AAgBA;AAiDA;AAUA;;;;;;;AAgBA;AAyCA;;AAEW,UApSM,QAAA,CAoSN;EAKN,OAAA,EAAA,MAAA;EAKQ,SAAA,EA5SA,IA4SA;EAAK,UAAA,EA3SJ,IA2SI;AASlB;AAiCA;AAcA;AAoCA;AAQiB,UAzYA,cAAA,CAyYiB;EAElB;EAEF,KAAA,EA3YL,GA2YK,CAAA,MAAA,EA3YO,QA2YP,CAAA;EAEkB;EAAZ,OAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EA3YK,QA2YL,GAAA,SAAA;EAEI;EAAgB,OAAA,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAsFvB;EAUA,UAAA,CAAA,IAAA,EAAA,MAAgB,CAAA,EAAA,OAAA;EAEnB;EAEK,SAAA,CAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA;EAEN;EAEG,KAAA,EAAA,EAAA,IAAA;;;;;AA+CA,iBAthBA,oBAAA,CAAA,CAshByB,EAthBD,cAshByB;;;;UA5ehD,eAAA;;;;;;;;mBAQE;;;;;;;;;;;;;;iBAYH,qBAAA,CAAA,GAAyB;;;;UAiDxB,eAAA;;;;;;;;;;;;;;;;;;;UAoBA,aAAA;;aAEJ,YAAY,kBAAkB;;mBAExB;;qCAEkB,kBAAkB;;4BAE3B;;;;;;;iBAQZ,mBAAA,CAAA,GAAuB;;;;UAiDtB,gBAAA;;;;;;;;;UAUA,gBAAA;;WAEN,YAAY;;iBAEN;;iCAEgB;;iDAEgB;;;;;;;iBAQjC,sBAAA,CAAA,GAA0B;;;;UAyCzB,iBAAA;;WAEN,YAAY;;;;;;;uDAKlB;;;;;;;+DAKQ;;;;;;;;;;;;iBASG,uBAAA,CAAA,GAA2B;;;;UAiC1B,kBAAA;;;;;;;;;;;;;iBAcD,wBAAA,CAAA,GAA4B;;;;UAoC3B,gBAAA;;;;;;;UAQA,iBAAA;;gBAED;;;;;;;cAEF;;oBAEM,YAAY;;wBAER;;;;;;;;;;;UAsFP,qBAAA;;;;;;;;;UAUA,gBAAA;;cAEH;;mBAEK;;aAEN;;gBAEG;;iBAEC;;kBAEC;;iBAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCD,cAAA,WAAyB,wBAAwB"}
|
package/dist/testing/index.mjs
CHANGED
|
@@ -172,9 +172,15 @@ function createMockBrowserTracker() {
|
|
|
172
172
|
get closeCount() {
|
|
173
173
|
return closeCount;
|
|
174
174
|
},
|
|
175
|
+
set closeCount(value) {
|
|
176
|
+
closeCount = value;
|
|
177
|
+
},
|
|
175
178
|
get isOpen() {
|
|
176
179
|
return isOpen;
|
|
177
180
|
},
|
|
181
|
+
set isOpen(value) {
|
|
182
|
+
isOpen = value;
|
|
183
|
+
},
|
|
178
184
|
reset() {
|
|
179
185
|
openedUrls.length = 0;
|
|
180
186
|
closeCount = 0;
|
|
@@ -183,6 +189,45 @@ function createMockBrowserTracker() {
|
|
|
183
189
|
};
|
|
184
190
|
}
|
|
185
191
|
/**
|
|
192
|
+
* Create a new dialog tracker instance
|
|
193
|
+
*/
|
|
194
|
+
function createMockDialogTracker() {
|
|
195
|
+
const shownDialogs = [];
|
|
196
|
+
const submittedResults = /* @__PURE__ */ new Map();
|
|
197
|
+
let nextResult = null;
|
|
198
|
+
return {
|
|
199
|
+
get shownDialogs() {
|
|
200
|
+
return shownDialogs;
|
|
201
|
+
},
|
|
202
|
+
get nextResult() {
|
|
203
|
+
return nextResult;
|
|
204
|
+
},
|
|
205
|
+
set nextResult(value) {
|
|
206
|
+
nextResult = value;
|
|
207
|
+
},
|
|
208
|
+
get submittedResults() {
|
|
209
|
+
return submittedResults;
|
|
210
|
+
},
|
|
211
|
+
setNextResult(result) {
|
|
212
|
+
nextResult = result;
|
|
213
|
+
},
|
|
214
|
+
submitResult(dialogId, value) {
|
|
215
|
+
submittedResults.set(dialogId, {
|
|
216
|
+
type: "submitted",
|
|
217
|
+
value
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
cancelDialog(dialogId) {
|
|
221
|
+
submittedResults.set(dialogId, { type: "cancelled" });
|
|
222
|
+
},
|
|
223
|
+
reset() {
|
|
224
|
+
shownDialogs.length = 0;
|
|
225
|
+
submittedResults.clear();
|
|
226
|
+
nextResult = null;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
186
231
|
* Extract filename from URL (mimics Rust backend behavior)
|
|
187
232
|
*/
|
|
188
233
|
function extractFilenameFromUrl(url) {
|
|
@@ -236,6 +281,7 @@ function setupMockTauri(options) {
|
|
|
236
281
|
const binaryConfig = createMockBinaryConfig();
|
|
237
282
|
const cookieStorage = createMockCookieStorage();
|
|
238
283
|
const browserTracker = createMockBrowserTracker();
|
|
284
|
+
const dialogTracker = createMockDialogTracker();
|
|
239
285
|
const invoke = async (cmd, args) => {
|
|
240
286
|
const payload = args;
|
|
241
287
|
switch (cmd) {
|
|
@@ -272,7 +318,7 @@ function setupMockTauri(options) {
|
|
|
272
318
|
const pn = payload?.pluginName;
|
|
273
319
|
const pp = payload?.projectPath;
|
|
274
320
|
const rp = payload?.relativePath;
|
|
275
|
-
const content = payload?.
|
|
321
|
+
const content = payload?.content;
|
|
276
322
|
const fullPath = `${pp}/.moss/plugins/${pn}/${rp}`;
|
|
277
323
|
filesystem.setFile(fullPath, content);
|
|
278
324
|
return null;
|
|
@@ -349,6 +395,30 @@ function setupMockTauri(options) {
|
|
|
349
395
|
browserTracker.closeCount++;
|
|
350
396
|
browserTracker.isOpen = false;
|
|
351
397
|
return null;
|
|
398
|
+
case "show_plugin_dialog": {
|
|
399
|
+
const url = payload?.url;
|
|
400
|
+
const title = payload?.title;
|
|
401
|
+
const width = payload?.width ?? 500;
|
|
402
|
+
const height = payload?.height ?? 400;
|
|
403
|
+
dialogTracker.shownDialogs.push({
|
|
404
|
+
url,
|
|
405
|
+
title,
|
|
406
|
+
width,
|
|
407
|
+
height
|
|
408
|
+
});
|
|
409
|
+
if (dialogTracker.nextResult) {
|
|
410
|
+
const result = dialogTracker.nextResult;
|
|
411
|
+
dialogTracker.nextResult = null;
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
return { type: "cancelled" };
|
|
415
|
+
}
|
|
416
|
+
case "submit_dialog_result": {
|
|
417
|
+
const dialogId = payload?.dialogId;
|
|
418
|
+
const result = payload?.result;
|
|
419
|
+
if (result) dialogTracker.submittedResults.set(dialogId, result);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
352
422
|
case "execute_binary": {
|
|
353
423
|
const binaryPath = payload?.binaryPath;
|
|
354
424
|
const binaryArgs = payload?.args;
|
|
@@ -381,6 +451,7 @@ function setupMockTauri(options) {
|
|
|
381
451
|
binaryConfig,
|
|
382
452
|
cookieStorage,
|
|
383
453
|
browserTracker,
|
|
454
|
+
dialogTracker,
|
|
384
455
|
projectPath,
|
|
385
456
|
pluginName,
|
|
386
457
|
cleanup: () => {
|
|
@@ -392,6 +463,7 @@ function setupMockTauri(options) {
|
|
|
392
463
|
binaryConfig.reset();
|
|
393
464
|
cookieStorage.clear();
|
|
394
465
|
browserTracker.reset();
|
|
466
|
+
dialogTracker.reset();
|
|
395
467
|
}
|
|
396
468
|
};
|
|
397
469
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["completedDownloads: string[]","failedDownloads: Array<{ url: string; error: string }>","defaultResponse: MockUrlResponse","defaultResult: MockBinaryResult","openedUrls: string[]","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 get isOpen() {\n return isOpen;\n },\n reset() {\n openedUrls.length = 0;\n closeCount = 0;\n isOpen = false;\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 /** 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\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?.data 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 // 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 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 },\n };\n}\n"],"mappings":";;;;AAqEA,SAAgB,uBAAuC;CACrD,MAAM,wBAAQ,IAAI,KAAuB;AAEzC,QAAO;EACL;EACA,QAAQ,MAAc;AACpB,UAAO,MAAM,IAAI,KAAK;;EAExB,QAAQ,MAAc,SAAiB;GACrC,MAAM,sBAAM,IAAI,MAAM;GACtB,MAAM,WAAW,MAAM,IAAI,KAAK;AAChC,SAAM,IAAI,MAAM;IACd;IACA,WAAW,UAAU,aAAa;IAClC,YAAY;IACb,CAAC;;EAEJ,WAAW,MAAc;AACvB,UAAO,MAAM,OAAO,KAAK;;EAE3B,UAAU,SAAkB;GAC1B,MAAM,WAAW,MAAM,KAAK,MAAM,MAAM,CAAC;AACzC,OAAI,CAAC,QAAS,QAAO;GAErB,MAAM,wBAAQ,IAAI,OAChB,MAAM,QAAQ,QAAQ,OAAO,KAAK,CAAC,QAAQ,OAAO,IAAI,GAAG,IAC1D;AACD,UAAO,SAAS,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;;EAE9C,QAAQ;AACN,SAAM,OAAO;;EAEhB;;;;;AA8BH,SAAgB,wBAAyC;CACvD,IAAI,kBAAkB;CACtB,IAAI,gBAAgB;CACpB,MAAMA,qBAA+B,EAAE;CACvC,MAAMC,kBAAyD,EAAE;AAEjE,QAAO;EACL,IAAI,kBAAkB;AACpB,UAAO;;EAET,IAAI,gBAAgB;AAClB,UAAO;;EAET,IAAI,qBAAqB;AACvB,UAAO;;EAET,IAAI,kBAAkB;AACpB,UAAO;;EAET,cAAc,KAAa;AACzB;AACA,OAAI,kBAAkB,cACpB,iBAAgB;;EAGpB,YAAY,KAAa,SAAkB,OAAgB;AACzD;AACA,OAAI,QACF,oBAAmB,KAAK,IAAI;OAE5B,iBAAgB,KAAK;IAAE;IAAK,OAAO,SAAS;IAAiB,CAAC;;EAGlE,QAAQ;AACN,qBAAkB;AAClB,mBAAgB;AAChB,sBAAmB,SAAS;AAC5B,mBAAgB,SAAS;;EAE5B;;;;;AA8CH,SAAgB,sBAAqC;CACnD,MAAM,4BAAY,IAAI,KAAkD;CACxE,MAAM,6BAAa,IAAI,KAAqB;CAG5C,MAAMC,kBAAmC;EACvC,QAAQ;EACR,IAAI;EACJ,aAAa;EACb,YACE;EACF,cAAc;EACd,YAAY;EACb;AAED,QAAO;EACL;EACA;EACA,YAAY,KAAa,UAA+C;AACtE,aAAU,IAAI,KAAK,SAAS;AAC5B,cAAW,IAAI,KAAK,EAAE;;EAExB,YAAY,KAA8B;GACxC,MAAM,SAAS,UAAU,IAAI,IAAI;AACjC,OAAI,CAAC,OAAQ,QAAO;AAEpB,OAAI,MAAM,QAAQ,OAAO,EAAE;IACzB,MAAM,QAAQ,WAAW,IAAI,IAAI,IAAI;AACrC,eAAW,IAAI,KAAK,QAAQ,EAAE;AAE9B,WAAO,OAAO,KAAK,IAAI,OAAO,OAAO,SAAS,EAAE;;AAGlD,UAAO;;EAET,QAAQ;AACN,aAAU,OAAO;AACjB,cAAW,OAAO;;EAErB;;;;;AAoCH,SAAgB,yBAA2C;CACzD,MAAM,0BAAU,IAAI,KAA+B;CAEnD,MAAMC,gBAAkC;EACtC,SAAS;EACT,UAAU;EACV,QAAQ;EACR,QAAQ;EACT;AAED,QAAO;EACL;EACA;EACA,UAAU,KAAa,QAA0B;AAC/C,WAAQ,IAAI,KAAK,OAAO;;EAE1B,UAAU,YAAoB,MAAkC;GAE9D,MAAM,WAAW,GAAG,WAAW,GAAG,KAAK,KAAK,IAAI,GAAG,MAAM;AACzD,OAAI,QAAQ,IAAI,SAAS,CACvB,QAAO,QAAQ,IAAI,SAAS;AAG9B,OAAI,QAAQ,IAAI,WAAW,CACzB,QAAO,QAAQ,IAAI,WAAW;AAEhC,UAAO;;EAET,QAAQ;AACN,WAAQ,OAAO;;EAElB;;;;;AA+BH,SAAgB,0BAA6C;CAC3D,MAAM,0BAAU,IAAI,KAGjB;AAEH,QAAO;EACL;EACA,WAAW,YAAoB,aAAqB;GAClD,MAAM,MAAM,GAAG,WAAW,GAAG;AAC7B,UAAO,QAAQ,IAAI,IAAI,IAAI,EAAE;;EAE/B,WACE,YACA,aACA,YACA;GACA,MAAM,MAAM,GAAG,WAAW,GAAG;AAC7B,WAAQ,IAAI,KAAK,WAAW;;EAE9B,QAAQ;AACN,WAAQ,OAAO;;EAElB;;;;;AAwBH,SAAgB,2BAA+C;CAC7D,MAAMC,aAAuB,EAAE;CAC/B,IAAI,aAAa;CACjB,IAAI,SAAS;AAEb,QAAO;EACL,IAAI,aAAa;AACf,UAAO;;EAET,IAAI,aAAa;AACf,UAAO;;EAET,IAAI,SAAS;AACX,UAAO;;EAET,QAAQ;AACN,cAAW,SAAS;AACpB,gBAAa;AACb,YAAS;;EAEZ;;;;;AAUH,SAAS,uBAAuB,KAAqB;AACnD,KAAI;EAGF,MAAM,WAFS,IAAI,IAAI,IAAI,CACH,SACE,MAAM,IAAI,CAAC,QAAQ,MAAM,EAAE,SAAS,EAAE;AAGhE,OAAK,MAAM,WAAW,SACpB,KACE,kEAAkE,KAChE,QACD,CAED,QAAO,GAAG,QAAQ;EAKtB,MAAM,cAAc,SAAS,SAAS,SAAS,MAAM;AACrD,SAAO,YAAY,SAAS,IAAI,GAAG,cAAc,GAAG,YAAY;SAC1D;AACN,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EX,SAAgB,eAAe,SAAmD;CAChF,MAAM,cAAc,SAAS,eAAe;CAC5C,MAAM,aAAa,SAAS,cAAc;CAE1C,MAAM,aAAa,sBAAsB;CACzC,MAAM,kBAAkB,uBAAuB;CAC/C,MAAM,YAAY,qBAAqB;CACvC,MAAM,eAAe,wBAAwB;CAC7C,MAAM,gBAAgB,yBAAyB;CAC/C,MAAM,iBAAiB,0BAA0B;CAGjD,MAAM,SAAS,OAAO,KAAa,SAAwC;EACzE,MAAM,UAAU;AAEhB,UAAQ,KAAR;GAIE,KAAK,qBAAqB;IAGxB,MAAM,WAAW,GAFG,SAAS,YAEG,GADX,SAAS;IAE9B,MAAM,OAAO,WAAW,QAAQ,SAAS;AACzC,QAAI,KACF,QAAO,KAAK;AAEd,UAAM,IAAI,MAAM,mBAAmB,WAAW;;GAGhD,KAAK,sBAAsB;IACzB,MAAMC,gBAAc,SAAS;IAC7B,MAAM,eAAe,SAAS;IAC9B,MAAM,UAAU,SAAS;IACzB,MAAM,WAAW,GAAGA,cAAY,GAAG;AACnC,eAAW,QAAQ,UAAU,QAAQ;AACrC,WAAO;;GAGT,KAAK,sBAAsB;IACzB,MAAMA,gBAAc,SAAS;AAG7B,WADiB,WAAW,WAAW,CAEpC,QAAQ,MAAM,EAAE,WAAWA,gBAAc,IAAI,CAAC,CAC9C,KAAK,MAAM,EAAE,UAAUA,cAAY,SAAS,EAAE,CAAC;;GAGpD,KAAK,uBAAuB;IAG1B,MAAM,WAAW,GAFG,SAAS,YAEG,GADX,SAAS;AAE9B,WAAO,WAAW,QAAQ,SAAS,KAAK;;GAM1C,KAAK,oBAAoB;IACvB,MAAM,KAAK,SAAS;IAGpB,MAAM,WAAW,GAFN,SAAS,YAEG,iBAAiB,GAAG,GADhC,SAAS;IAEpB,MAAM,OAAO,WAAW,QAAQ,SAAS;AACzC,QAAI,KACF,QAAO,KAAK;AAEd,UAAM,IAAI,MAAM,0BAA0B,WAAW;;GAGvD,KAAK,qBAAqB;IACxB,MAAM,KAAK,SAAS;IACpB,MAAM,KAAK,SAAS;IACpB,MAAM,KAAK,SAAS;IACpB,MAAM,UAAU,SAAS;IACzB,MAAM,WAAW,GAAG,GAAG,iBAAiB,GAAG,GAAG;AAC9C,eAAW,QAAQ,UAAU,QAAQ;AACrC,WAAO;;GAGT,KAAK,qBAAqB;IACxB,MAAM,KAAK,SAAS;IAEpB,MAAM,SAAS,GADJ,SAAS,YACC,iBAAiB,GAAG;AAEzC,WADiB,WAAW,WAAW,CAEpC,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC,CACnC,KAAK,MAAM,EAAE,UAAU,OAAO,OAAO,CAAC;;GAG3C,KAAK,sBAAsB;IACzB,MAAM,KAAK,SAAS;IAGpB,MAAM,WAAW,GAFN,SAAS,YAEG,iBAAiB,GAAG,GADhC,SAAS;AAEpB,WAAO,WAAW,QAAQ,SAAS,KAAK;;GAM1C,KAAK,aAAa;IAChB,MAAM,MAAM,SAAS;IACrB,MAAM,WAAW,UAAU,YAAY,IAAI;AAE3C,QAAI,SAAS,MACX,QAAO,IAAI,SAAS,YAClB,iBAEI,QAAQ;KACN,QAAQ,SAAS;KACjB,IAAI,SAAS;KACb,aAAa,SAAS,cAAc;KACpC,cAAc,SAAS,eAAe;KACvC,CAAC,EACJ,SAAS,MACV,CACF;AAGH,WAAO;KACL,QAAQ,SAAS;KACjB,IAAI,SAAS;KACb,aAAa,SAAS,cAAc;KACpC,cAAc,SAAS,eAAe;KACvC;;GAGH,KAAK,kBAAkB;IACrB,MAAM,MAAM,SAAS;IACrB,MAAM,YAAY,SAAS;IAC3B,MAAM,WAAW,UAAU,YAAY,IAAI;AAE3C,oBAAgB,cAAc,IAAI;AAGlC,QAAI,SAAS,WAAW,GAAG;AACzB,qBAAgB,YAAY,KAAK,OAAO,gBAAgB;AACxD,WAAM,IAAI,MAAM,kBAAkB;;IAIpC,MAAM,aACJ,SAAS,cAAc,GAAG,UAAU,GAAG,uBAAuB,IAAI;IAEpE,MAAM,SAAS;KACb,QAAQ,SAAS;KACjB,IAAI,SAAS;KACb,cAAc,SAAS,eAAe;KACtC,eAAe,SAAS,gBAAgB;KACxC,aAAa;KACd;AAED,QAAI,SAAS,MACX,QAAO,IAAI,SAAS,YAClB,iBAAiB;AACf,qBAAgB,YAAY,KAAK,SAAS,GAAG;AAC7C,aAAQ,OAAO;OACd,SAAS,MAAM,CACnB;AAGH,oBAAgB,YAAY,KAAK,SAAS,GAAG;AAC7C,WAAO;;GAMT,KAAK,qBAAqB;IACxB,MAAMC,eAAa,SAAS;IAC5B,MAAMD,gBAAc,SAAS;AAC7B,WAAO,cAAc,WAAWC,cAAYD,cAAY;;GAG1D,KAAK,qBAAqB;IACxB,MAAMC,eAAa,SAAS;IAC5B,MAAMD,gBAAc,SAAS;IAC7B,MAAM,UAAU,SAAS;AAMzB,kBAAc,WAAWC,cAAYD,eAAa,QAAQ;AAC1D,WAAO;;GAMT,KAAK,uBAAuB;IAC1B,MAAM,MAAM,SAAS;AACrB,mBAAe,WAAW,KAAK,IAAI;AACnC,IAAC,eAAuC,SAAS;AACjD,WAAO;;GAGT,KAAK;AACH,IAAC,eAA0C;AAC3C,IAAC,eAAuC,SAAS;AACjD,WAAO;GAMT,KAAK,kBAAkB;IACrB,MAAM,aAAa,SAAS;IAC5B,MAAM,aAAa,SAAS;IAC5B,MAAM,SAAS,aAAa,UAAU,YAAY,WAAW;AAE7D,WAAO;KACL,SAAS,OAAO;KAChB,WAAW,OAAO;KAClB,QAAQ,OAAO;KACf,QAAQ,OAAO;KAChB;;GAMH,KAAK,iBAEH,QAAO;GAGT;AACE,YAAQ,KAAK,0BAA0B,MAAM;AAC7C,WAAO;;;AAiBb,KAAI,OAZM,WAYG,WAAW,YACtB,CAAC,WAA6C,SAAS,EAAE;CAG3D,MAAM,MAAO,WASV;AAEH,KAAI,YAAY,EACd,MAAM,EAAE,QAAQ,EACjB;AAGD,KAAI,4BAA4B;EAC9B,aAAa;EACb,cAAc;EACd,UAAU,GAAG,YAAY;EAC1B;AAED,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eAAe;AAEb,UAAO,IAAI;AACX,UAAO,IAAI;AACX,cAAW,OAAO;AAClB,mBAAgB,OAAO;AACvB,aAAU,OAAO;AACjB,gBAAa,OAAO;AACpB,iBAAc,OAAO;AACrB,kBAAe,OAAO;;EAEzB"}
|
|
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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"}
|