@symbiosis-lab/moss-api 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +141 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +202 -44
- package/dist/index.mjs.map +1 -1
- package/dist/testing/index.d.mts +20 -5
- package/dist/testing/index.d.mts.map +1 -1
- package/dist/testing/index.mjs +26 -14
- package/dist/testing/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -21,17 +21,20 @@ interface PluginManifest {
|
|
|
21
21
|
type PluginCategory = "generator" | "deployer" | "syndicator" | "enhancer" | "processor";
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region src/types/context.d.ts
|
|
24
|
+
|
|
24
25
|
/**
|
|
25
26
|
* Base context shared by all hooks
|
|
27
|
+
*
|
|
28
|
+
* Contains only business data - no paths.
|
|
29
|
+
* Use readFile(), writeFile() for project files.
|
|
30
|
+
* Use readPluginFile(), writePluginFile() for plugin storage.
|
|
26
31
|
*/
|
|
27
32
|
interface BaseContext {
|
|
28
|
-
project_path: string;
|
|
29
|
-
moss_dir: string;
|
|
30
33
|
project_info: ProjectInfo;
|
|
31
34
|
config: Record<string, unknown>;
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
|
-
* Context for before_build hook
|
|
37
|
+
* Context for before_build hook (process capability)
|
|
35
38
|
*/
|
|
36
39
|
interface BeforeBuildContext extends BaseContext {}
|
|
37
40
|
/**
|
|
@@ -44,14 +47,12 @@ interface OnBuildContext extends BaseContext {
|
|
|
44
47
|
* Context for on_deploy hook (deployer plugins)
|
|
45
48
|
*/
|
|
46
49
|
interface OnDeployContext extends BaseContext {
|
|
47
|
-
output_dir: string;
|
|
48
50
|
site_files: string[];
|
|
49
51
|
}
|
|
50
52
|
/**
|
|
51
53
|
* Context for after_deploy hook (syndicator plugins)
|
|
52
54
|
*/
|
|
53
55
|
interface AfterDeployContext extends BaseContext {
|
|
54
|
-
output_dir: string;
|
|
55
56
|
site_files: string[];
|
|
56
57
|
articles: ArticleInfo[];
|
|
57
58
|
deployment?: DeploymentInfo;
|
|
@@ -218,70 +219,170 @@ declare function closeBrowser(): Promise<void>;
|
|
|
218
219
|
/**
|
|
219
220
|
* File system operations for Moss plugins
|
|
220
221
|
*
|
|
221
|
-
* These functions
|
|
222
|
-
*
|
|
222
|
+
* These functions provide access to project files (user content).
|
|
223
|
+
* Project path is auto-detected from the runtime context.
|
|
224
|
+
*
|
|
225
|
+
* For plugin's private storage, use the plugin-storage API instead.
|
|
223
226
|
*/
|
|
224
227
|
/**
|
|
225
228
|
* Read a file from the project directory
|
|
226
229
|
*
|
|
227
|
-
*
|
|
230
|
+
* Project path is auto-detected from the runtime context.
|
|
231
|
+
*
|
|
228
232
|
* @param relativePath - Path relative to the project root
|
|
229
233
|
* @returns File contents as a string
|
|
230
|
-
* @throws Error if file cannot be read
|
|
234
|
+
* @throws Error if file cannot be read or called outside a hook
|
|
231
235
|
*
|
|
232
236
|
* @example
|
|
233
237
|
* ```typescript
|
|
234
|
-
*
|
|
238
|
+
* // Read an article
|
|
239
|
+
* const content = await readFile("article/hello-world.md");
|
|
240
|
+
*
|
|
241
|
+
* // Read package.json
|
|
242
|
+
* const pkg = JSON.parse(await readFile("package.json"));
|
|
235
243
|
* ```
|
|
236
244
|
*/
|
|
237
|
-
declare function readFile(
|
|
245
|
+
declare function readFile(relativePath: string): Promise<string>;
|
|
238
246
|
/**
|
|
239
247
|
* Write content to a file in the project directory
|
|
240
248
|
*
|
|
241
249
|
* Creates parent directories if they don't exist.
|
|
250
|
+
* Project path is auto-detected from the runtime context.
|
|
242
251
|
*
|
|
243
|
-
* @param projectPath - Absolute path to the project directory
|
|
244
252
|
* @param relativePath - Path relative to the project root
|
|
245
253
|
* @param content - Content to write to the file
|
|
246
|
-
* @throws Error if file cannot be written
|
|
254
|
+
* @throws Error if file cannot be written or called outside a hook
|
|
247
255
|
*
|
|
248
256
|
* @example
|
|
249
257
|
* ```typescript
|
|
250
|
-
*
|
|
258
|
+
* // Write a generated article
|
|
259
|
+
* await writeFile("article/new-post.md", "# Hello World\n\nContent here.");
|
|
260
|
+
*
|
|
261
|
+
* // Write index page
|
|
262
|
+
* await writeFile("index.md", markdownContent);
|
|
251
263
|
* ```
|
|
252
264
|
*/
|
|
253
|
-
declare function writeFile(
|
|
265
|
+
declare function writeFile(relativePath: string, content: string): Promise<void>;
|
|
254
266
|
/**
|
|
255
|
-
* List all files in
|
|
267
|
+
* List all files in the project directory
|
|
256
268
|
*
|
|
257
269
|
* Returns file paths relative to the project root.
|
|
270
|
+
* Project path is auto-detected from the runtime context.
|
|
258
271
|
*
|
|
259
|
-
* @param projectPath - Absolute path to the project directory
|
|
260
272
|
* @returns Array of relative file paths
|
|
261
|
-
* @throws Error if directory cannot be listed
|
|
273
|
+
* @throws Error if directory cannot be listed or called outside a hook
|
|
262
274
|
*
|
|
263
275
|
* @example
|
|
264
276
|
* ```typescript
|
|
265
|
-
* const files = await listFiles(
|
|
266
|
-
* // ["
|
|
277
|
+
* const files = await listFiles();
|
|
278
|
+
* // ["index.md", "article/hello.md", "assets/logo.png"]
|
|
279
|
+
*
|
|
280
|
+
* const mdFiles = files.filter(f => f.endsWith(".md"));
|
|
267
281
|
* ```
|
|
268
282
|
*/
|
|
269
|
-
declare function listFiles(
|
|
283
|
+
declare function listFiles(): Promise<string[]>;
|
|
270
284
|
/**
|
|
271
285
|
* Check if a file exists in the project directory
|
|
272
286
|
*
|
|
273
|
-
*
|
|
287
|
+
* Project path is auto-detected from the runtime context.
|
|
288
|
+
*
|
|
274
289
|
* @param relativePath - Path relative to the project root
|
|
275
290
|
* @returns true if file exists, false otherwise
|
|
291
|
+
* @throws Error if called outside a hook
|
|
276
292
|
*
|
|
277
293
|
* @example
|
|
278
294
|
* ```typescript
|
|
279
|
-
* if (await fileExists("
|
|
280
|
-
*
|
|
295
|
+
* if (await fileExists("index.md")) {
|
|
296
|
+
* const content = await readFile("index.md");
|
|
281
297
|
* }
|
|
282
298
|
* ```
|
|
283
299
|
*/
|
|
284
|
-
declare function fileExists(
|
|
300
|
+
declare function fileExists(relativePath: string): Promise<boolean>;
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/utils/plugin-storage.d.ts
|
|
303
|
+
/**
|
|
304
|
+
* Plugin storage API for Moss plugins
|
|
305
|
+
*
|
|
306
|
+
* Provides access to a plugin's private storage directory at:
|
|
307
|
+
* .moss/plugins/{plugin-name}/
|
|
308
|
+
*
|
|
309
|
+
* Plugin identity is auto-detected from the runtime context -
|
|
310
|
+
* plugins never need to know their own name or path.
|
|
311
|
+
*
|
|
312
|
+
* Config is just a file: readPluginFile("config.json")
|
|
313
|
+
*/
|
|
314
|
+
/**
|
|
315
|
+
* Read a file from the plugin's private storage directory
|
|
316
|
+
*
|
|
317
|
+
* Storage path: .moss/plugins/{plugin-name}/{relativePath}
|
|
318
|
+
*
|
|
319
|
+
* @param relativePath - Path relative to the plugin's storage directory
|
|
320
|
+
* @returns File contents as a string
|
|
321
|
+
* @throws Error if file cannot be read or called outside a hook
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* // Read plugin config
|
|
326
|
+
* const configJson = await readPluginFile("config.json");
|
|
327
|
+
* const config = JSON.parse(configJson);
|
|
328
|
+
*
|
|
329
|
+
* // Read cached data
|
|
330
|
+
* const cached = await readPluginFile("cache/articles.json");
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
declare function readPluginFile(relativePath: string): Promise<string>;
|
|
334
|
+
/**
|
|
335
|
+
* Write a file to the plugin's private storage directory
|
|
336
|
+
*
|
|
337
|
+
* Creates parent directories if they don't exist.
|
|
338
|
+
* Storage path: .moss/plugins/{plugin-name}/{relativePath}
|
|
339
|
+
*
|
|
340
|
+
* @param relativePath - Path relative to the plugin's storage directory
|
|
341
|
+
* @param content - Content to write to the file
|
|
342
|
+
* @throws Error if file cannot be written or called outside a hook
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```typescript
|
|
346
|
+
* // Save plugin config
|
|
347
|
+
* await writePluginFile("config.json", JSON.stringify(config, null, 2));
|
|
348
|
+
*
|
|
349
|
+
* // Cache data
|
|
350
|
+
* await writePluginFile("cache/articles.json", JSON.stringify(articles));
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
declare function writePluginFile(relativePath: string, content: string): Promise<void>;
|
|
354
|
+
/**
|
|
355
|
+
* List all files in the plugin's private storage directory
|
|
356
|
+
*
|
|
357
|
+
* Returns file paths relative to the plugin's storage directory.
|
|
358
|
+
*
|
|
359
|
+
* @returns Array of relative file paths
|
|
360
|
+
* @throws Error if directory cannot be listed or called outside a hook
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```typescript
|
|
364
|
+
* const files = await listPluginFiles();
|
|
365
|
+
* // ["config.json", "cache/articles.json", "cache/images.json"]
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
declare function listPluginFiles(): Promise<string[]>;
|
|
369
|
+
/**
|
|
370
|
+
* Check if a file exists in the plugin's private storage directory
|
|
371
|
+
*
|
|
372
|
+
* @param relativePath - Path relative to the plugin's storage directory
|
|
373
|
+
* @returns true if file exists, false otherwise
|
|
374
|
+
* @throws Error if called outside a hook
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* if (await pluginFileExists("config.json")) {
|
|
379
|
+
* const config = JSON.parse(await readPluginFile("config.json"));
|
|
380
|
+
* } else {
|
|
381
|
+
* // Use default config
|
|
382
|
+
* }
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
declare function pluginFileExists(relativePath: string): Promise<boolean>;
|
|
285
386
|
//#endregion
|
|
286
387
|
//#region src/utils/http.d.ts
|
|
287
388
|
/**
|
|
@@ -289,6 +390,8 @@ declare function fileExists(projectPath: string, relativePath: string): Promise<
|
|
|
289
390
|
*
|
|
290
391
|
* These functions provide HTTP capabilities that bypass browser CORS
|
|
291
392
|
* restrictions by using Rust's HTTP client under the hood.
|
|
393
|
+
*
|
|
394
|
+
* Project path for downloads is auto-detected from the runtime context.
|
|
292
395
|
*/
|
|
293
396
|
/**
|
|
294
397
|
* Options for HTTP fetch requests
|
|
@@ -358,18 +461,18 @@ declare function fetchUrl(url: string, options?: FetchOptions): Promise<FetchRes
|
|
|
358
461
|
* the binary data through JavaScript. The filename is derived from
|
|
359
462
|
* the URL, and file extension is inferred from Content-Type if needed.
|
|
360
463
|
*
|
|
464
|
+
* Project path is auto-detected from the runtime context.
|
|
465
|
+
*
|
|
361
466
|
* @param url - URL to download
|
|
362
|
-
* @param projectPath - Absolute path to the project directory
|
|
363
467
|
* @param targetDir - Target directory within project (e.g., "assets")
|
|
364
468
|
* @param options - Optional download configuration
|
|
365
469
|
* @returns Download result with actual path where file was saved
|
|
366
|
-
* @throws Error if download or write fails
|
|
470
|
+
* @throws Error if download or write fails, or called outside a hook
|
|
367
471
|
*
|
|
368
472
|
* @example
|
|
369
473
|
* ```typescript
|
|
370
474
|
* const result = await downloadAsset(
|
|
371
475
|
* "https://example.com/image",
|
|
372
|
-
* "/path/to/project",
|
|
373
476
|
* "assets"
|
|
374
477
|
* );
|
|
375
478
|
* if (result.ok) {
|
|
@@ -377,7 +480,7 @@ declare function fetchUrl(url: string, options?: FetchOptions): Promise<FetchRes
|
|
|
377
480
|
* }
|
|
378
481
|
* ```
|
|
379
482
|
*/
|
|
380
|
-
declare function downloadAsset(url: string,
|
|
483
|
+
declare function downloadAsset(url: string, targetDir: string, options?: DownloadOptions): Promise<DownloadResult>;
|
|
381
484
|
//#endregion
|
|
382
485
|
//#region src/utils/binary.d.ts
|
|
383
486
|
/**
|
|
@@ -385,6 +488,9 @@ declare function downloadAsset(url: string, projectPath: string, targetDir: stri
|
|
|
385
488
|
*
|
|
386
489
|
* Allows plugins to execute external binaries (git, npm, etc.)
|
|
387
490
|
* in a controlled environment.
|
|
491
|
+
*
|
|
492
|
+
* Working directory is auto-detected from the runtime context
|
|
493
|
+
* (always the project root).
|
|
388
494
|
*/
|
|
389
495
|
/**
|
|
390
496
|
* Options for executing a binary
|
|
@@ -394,8 +500,6 @@ interface ExecuteOptions {
|
|
|
394
500
|
binaryPath: string;
|
|
395
501
|
/** Arguments to pass to the binary */
|
|
396
502
|
args: string[];
|
|
397
|
-
/** Working directory for execution */
|
|
398
|
-
workingDir: string;
|
|
399
503
|
/** Timeout in milliseconds (default: 60000) */
|
|
400
504
|
timeoutMs?: number;
|
|
401
505
|
/** Additional environment variables */
|
|
@@ -417,9 +521,12 @@ interface ExecuteResult {
|
|
|
417
521
|
/**
|
|
418
522
|
* Execute an external binary
|
|
419
523
|
*
|
|
420
|
-
*
|
|
524
|
+
* Working directory is auto-detected from the runtime context
|
|
525
|
+
* (always the project root).
|
|
526
|
+
*
|
|
527
|
+
* @param options - Execution options including binary path and args
|
|
421
528
|
* @returns Execution result with stdout, stderr, and exit code
|
|
422
|
-
* @throws Error if binary cannot be executed
|
|
529
|
+
* @throws Error if binary cannot be executed or called outside a hook
|
|
423
530
|
*
|
|
424
531
|
* @example
|
|
425
532
|
* ```typescript
|
|
@@ -427,7 +534,6 @@ interface ExecuteResult {
|
|
|
427
534
|
* const result = await executeBinary({
|
|
428
535
|
* binaryPath: "git",
|
|
429
536
|
* args: ["status"],
|
|
430
|
-
* workingDir: "/path/to/repo",
|
|
431
537
|
* });
|
|
432
538
|
*
|
|
433
539
|
* if (result.success) {
|
|
@@ -443,7 +549,6 @@ interface ExecuteResult {
|
|
|
443
549
|
* const result = await executeBinary({
|
|
444
550
|
* binaryPath: "npm",
|
|
445
551
|
* args: ["install"],
|
|
446
|
-
* workingDir: "/path/to/project",
|
|
447
552
|
* timeoutMs: 120000,
|
|
448
553
|
* env: { NODE_ENV: "production" },
|
|
449
554
|
* });
|
|
@@ -516,5 +621,5 @@ declare function getPluginCookie(): Promise<Cookie[]>;
|
|
|
516
621
|
*/
|
|
517
622
|
declare function setPluginCookie(cookies: Cookie[]): Promise<void>;
|
|
518
623
|
//#endregion
|
|
519
|
-
export { AfterDeployContext, ArticleInfo, BaseContext, BeforeBuildContext, CompleteMessage, Cookie, DeploymentInfo, DownloadOptions, DownloadResult, ErrorMessage, ExecuteOptions, ExecuteResult, FetchOptions, FetchResult, HookResult, LogMessage, OnBuildContext, OnDeployContext, PluginCategory, PluginManifest, PluginMessage, ProgressMessage, ProjectInfo, SourceFiles, TauriCore, closeBrowser, downloadAsset, error, executeBinary, fetchUrl, fileExists, getMessageContext, getPluginCookie, getTauriCore, isTauriAvailable, listFiles, log, openBrowser, readFile, reportComplete, reportError, reportProgress, sendMessage, setMessageContext, setPluginCookie, warn, writeFile };
|
|
624
|
+
export { AfterDeployContext, ArticleInfo, BaseContext, BeforeBuildContext, CompleteMessage, Cookie, DeploymentInfo, DownloadOptions, DownloadResult, ErrorMessage, ExecuteOptions, ExecuteResult, FetchOptions, FetchResult, HookResult, LogMessage, OnBuildContext, OnDeployContext, PluginCategory, PluginManifest, PluginMessage, ProgressMessage, ProjectInfo, SourceFiles, TauriCore, closeBrowser, downloadAsset, error, executeBinary, fetchUrl, fileExists, getMessageContext, getPluginCookie, getTauriCore, isTauriAvailable, listFiles, listPluginFiles, log, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, sendMessage, setMessageContext, setPluginCookie, warn, writeFile, writePluginFile };
|
|
520
625
|
//# 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/http.ts","../src/utils/binary.ts","../src/utils/cookies.ts"],"sourcesContent":[],"mappings":";;AAIA;AAOA;AAWY,UAlBK,WAAA,CAkBS;;;;
|
|
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":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -145,30 +145,57 @@ async function closeBrowser() {
|
|
|
145
145
|
await getTauriCore().invoke("close_plugin_browser", {});
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/utils/context.ts
|
|
150
|
+
/**
|
|
151
|
+
* Get the internal plugin context
|
|
152
|
+
*
|
|
153
|
+
* This is used internally by moss-api utilities to resolve paths
|
|
154
|
+
* and plugin identity. Plugins should not call this directly.
|
|
155
|
+
*
|
|
156
|
+
* @returns The current plugin execution context
|
|
157
|
+
* @throws Error if called outside of a plugin hook execution
|
|
158
|
+
*
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
function getInternalContext() {
|
|
162
|
+
const context = window.__MOSS_INTERNAL_CONTEXT__;
|
|
163
|
+
if (!context) throw new Error("This function must be called from within a plugin hook. Ensure you're calling this from process(), generate(), deploy(), or syndicate().");
|
|
164
|
+
return context;
|
|
165
|
+
}
|
|
166
|
+
|
|
148
167
|
//#endregion
|
|
149
168
|
//#region src/utils/filesystem.ts
|
|
150
169
|
/**
|
|
151
170
|
* File system operations for Moss plugins
|
|
152
171
|
*
|
|
153
|
-
* These functions
|
|
154
|
-
*
|
|
172
|
+
* These functions provide access to project files (user content).
|
|
173
|
+
* Project path is auto-detected from the runtime context.
|
|
174
|
+
*
|
|
175
|
+
* For plugin's private storage, use the plugin-storage API instead.
|
|
155
176
|
*/
|
|
156
177
|
/**
|
|
157
178
|
* Read a file from the project directory
|
|
158
179
|
*
|
|
159
|
-
*
|
|
180
|
+
* Project path is auto-detected from the runtime context.
|
|
181
|
+
*
|
|
160
182
|
* @param relativePath - Path relative to the project root
|
|
161
183
|
* @returns File contents as a string
|
|
162
|
-
* @throws Error if file cannot be read
|
|
184
|
+
* @throws Error if file cannot be read or called outside a hook
|
|
163
185
|
*
|
|
164
186
|
* @example
|
|
165
187
|
* ```typescript
|
|
166
|
-
*
|
|
188
|
+
* // Read an article
|
|
189
|
+
* const content = await readFile("article/hello-world.md");
|
|
190
|
+
*
|
|
191
|
+
* // Read package.json
|
|
192
|
+
* const pkg = JSON.parse(await readFile("package.json"));
|
|
167
193
|
* ```
|
|
168
194
|
*/
|
|
169
|
-
async function readFile(
|
|
195
|
+
async function readFile(relativePath) {
|
|
196
|
+
const ctx = getInternalContext();
|
|
170
197
|
return getTauriCore().invoke("read_project_file", {
|
|
171
|
-
projectPath,
|
|
198
|
+
projectPath: ctx.project_path,
|
|
172
199
|
relativePath
|
|
173
200
|
});
|
|
174
201
|
}
|
|
@@ -176,65 +203,190 @@ async function readFile(projectPath, relativePath) {
|
|
|
176
203
|
* Write content to a file in the project directory
|
|
177
204
|
*
|
|
178
205
|
* Creates parent directories if they don't exist.
|
|
206
|
+
* Project path is auto-detected from the runtime context.
|
|
179
207
|
*
|
|
180
|
-
* @param projectPath - Absolute path to the project directory
|
|
181
208
|
* @param relativePath - Path relative to the project root
|
|
182
209
|
* @param content - Content to write to the file
|
|
183
|
-
* @throws Error if file cannot be written
|
|
210
|
+
* @throws Error if file cannot be written or called outside a hook
|
|
184
211
|
*
|
|
185
212
|
* @example
|
|
186
213
|
* ```typescript
|
|
187
|
-
*
|
|
214
|
+
* // Write a generated article
|
|
215
|
+
* await writeFile("article/new-post.md", "# Hello World\n\nContent here.");
|
|
216
|
+
*
|
|
217
|
+
* // Write index page
|
|
218
|
+
* await writeFile("index.md", markdownContent);
|
|
188
219
|
* ```
|
|
189
220
|
*/
|
|
190
|
-
async function writeFile(
|
|
221
|
+
async function writeFile(relativePath, content) {
|
|
222
|
+
const ctx = getInternalContext();
|
|
191
223
|
await getTauriCore().invoke("write_project_file", {
|
|
192
|
-
projectPath,
|
|
224
|
+
projectPath: ctx.project_path,
|
|
193
225
|
relativePath,
|
|
194
226
|
data: content
|
|
195
227
|
});
|
|
196
228
|
}
|
|
197
229
|
/**
|
|
198
|
-
* List all files in
|
|
230
|
+
* List all files in the project directory
|
|
199
231
|
*
|
|
200
232
|
* Returns file paths relative to the project root.
|
|
233
|
+
* Project path is auto-detected from the runtime context.
|
|
201
234
|
*
|
|
202
|
-
* @param projectPath - Absolute path to the project directory
|
|
203
235
|
* @returns Array of relative file paths
|
|
204
|
-
* @throws Error if directory cannot be listed
|
|
236
|
+
* @throws Error if directory cannot be listed or called outside a hook
|
|
205
237
|
*
|
|
206
238
|
* @example
|
|
207
239
|
* ```typescript
|
|
208
|
-
* const files = await listFiles(
|
|
209
|
-
* // ["
|
|
240
|
+
* const files = await listFiles();
|
|
241
|
+
* // ["index.md", "article/hello.md", "assets/logo.png"]
|
|
242
|
+
*
|
|
243
|
+
* const mdFiles = files.filter(f => f.endsWith(".md"));
|
|
210
244
|
* ```
|
|
211
245
|
*/
|
|
212
|
-
async function listFiles(
|
|
213
|
-
|
|
246
|
+
async function listFiles() {
|
|
247
|
+
const ctx = getInternalContext();
|
|
248
|
+
return getTauriCore().invoke("list_project_files", { projectPath: ctx.project_path });
|
|
214
249
|
}
|
|
215
250
|
/**
|
|
216
251
|
* Check if a file exists in the project directory
|
|
217
252
|
*
|
|
218
|
-
*
|
|
253
|
+
* Project path is auto-detected from the runtime context.
|
|
254
|
+
*
|
|
219
255
|
* @param relativePath - Path relative to the project root
|
|
220
256
|
* @returns true if file exists, false otherwise
|
|
257
|
+
* @throws Error if called outside a hook
|
|
221
258
|
*
|
|
222
259
|
* @example
|
|
223
260
|
* ```typescript
|
|
224
|
-
* if (await fileExists("
|
|
225
|
-
*
|
|
261
|
+
* if (await fileExists("index.md")) {
|
|
262
|
+
* const content = await readFile("index.md");
|
|
226
263
|
* }
|
|
227
264
|
* ```
|
|
228
265
|
*/
|
|
229
|
-
async function fileExists(
|
|
266
|
+
async function fileExists(relativePath) {
|
|
267
|
+
getInternalContext();
|
|
230
268
|
try {
|
|
231
|
-
await readFile(
|
|
269
|
+
await readFile(relativePath);
|
|
232
270
|
return true;
|
|
233
271
|
} catch {
|
|
234
272
|
return false;
|
|
235
273
|
}
|
|
236
274
|
}
|
|
237
275
|
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/utils/plugin-storage.ts
|
|
278
|
+
/**
|
|
279
|
+
* Plugin storage API for Moss plugins
|
|
280
|
+
*
|
|
281
|
+
* Provides access to a plugin's private storage directory at:
|
|
282
|
+
* .moss/plugins/{plugin-name}/
|
|
283
|
+
*
|
|
284
|
+
* Plugin identity is auto-detected from the runtime context -
|
|
285
|
+
* plugins never need to know their own name or path.
|
|
286
|
+
*
|
|
287
|
+
* Config is just a file: readPluginFile("config.json")
|
|
288
|
+
*/
|
|
289
|
+
/**
|
|
290
|
+
* Read a file from the plugin's private storage directory
|
|
291
|
+
*
|
|
292
|
+
* Storage path: .moss/plugins/{plugin-name}/{relativePath}
|
|
293
|
+
*
|
|
294
|
+
* @param relativePath - Path relative to the plugin's storage directory
|
|
295
|
+
* @returns File contents as a string
|
|
296
|
+
* @throws Error if file cannot be read or called outside a hook
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```typescript
|
|
300
|
+
* // Read plugin config
|
|
301
|
+
* const configJson = await readPluginFile("config.json");
|
|
302
|
+
* const config = JSON.parse(configJson);
|
|
303
|
+
*
|
|
304
|
+
* // Read cached data
|
|
305
|
+
* const cached = await readPluginFile("cache/articles.json");
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
async function readPluginFile(relativePath) {
|
|
309
|
+
const ctx = getInternalContext();
|
|
310
|
+
return getTauriCore().invoke("read_plugin_file", {
|
|
311
|
+
pluginName: ctx.plugin_name,
|
|
312
|
+
projectPath: ctx.project_path,
|
|
313
|
+
relativePath
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Write a file to the plugin's private storage directory
|
|
318
|
+
*
|
|
319
|
+
* Creates parent directories if they don't exist.
|
|
320
|
+
* Storage path: .moss/plugins/{plugin-name}/{relativePath}
|
|
321
|
+
*
|
|
322
|
+
* @param relativePath - Path relative to the plugin's storage directory
|
|
323
|
+
* @param content - Content to write to the file
|
|
324
|
+
* @throws Error if file cannot be written or called outside a hook
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* // Save plugin config
|
|
329
|
+
* await writePluginFile("config.json", JSON.stringify(config, null, 2));
|
|
330
|
+
*
|
|
331
|
+
* // Cache data
|
|
332
|
+
* await writePluginFile("cache/articles.json", JSON.stringify(articles));
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
async function writePluginFile(relativePath, content) {
|
|
336
|
+
const ctx = getInternalContext();
|
|
337
|
+
await getTauriCore().invoke("write_plugin_file", {
|
|
338
|
+
pluginName: ctx.plugin_name,
|
|
339
|
+
projectPath: ctx.project_path,
|
|
340
|
+
relativePath,
|
|
341
|
+
content
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* List all files in the plugin's private storage directory
|
|
346
|
+
*
|
|
347
|
+
* Returns file paths relative to the plugin's storage directory.
|
|
348
|
+
*
|
|
349
|
+
* @returns Array of relative file paths
|
|
350
|
+
* @throws Error if directory cannot be listed or called outside a hook
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* const files = await listPluginFiles();
|
|
355
|
+
* // ["config.json", "cache/articles.json", "cache/images.json"]
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
async function listPluginFiles() {
|
|
359
|
+
const ctx = getInternalContext();
|
|
360
|
+
return getTauriCore().invoke("list_plugin_files", {
|
|
361
|
+
pluginName: ctx.plugin_name,
|
|
362
|
+
projectPath: ctx.project_path
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check if a file exists in the plugin's private storage directory
|
|
367
|
+
*
|
|
368
|
+
* @param relativePath - Path relative to the plugin's storage directory
|
|
369
|
+
* @returns true if file exists, false otherwise
|
|
370
|
+
* @throws Error if called outside a hook
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* if (await pluginFileExists("config.json")) {
|
|
375
|
+
* const config = JSON.parse(await readPluginFile("config.json"));
|
|
376
|
+
* } else {
|
|
377
|
+
* // Use default config
|
|
378
|
+
* }
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
async function pluginFileExists(relativePath) {
|
|
382
|
+
const ctx = getInternalContext();
|
|
383
|
+
return getTauriCore().invoke("plugin_file_exists", {
|
|
384
|
+
pluginName: ctx.plugin_name,
|
|
385
|
+
projectPath: ctx.project_path,
|
|
386
|
+
relativePath
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
238
390
|
//#endregion
|
|
239
391
|
//#region src/utils/http.ts
|
|
240
392
|
/**
|
|
@@ -242,6 +394,8 @@ async function fileExists(projectPath, relativePath) {
|
|
|
242
394
|
*
|
|
243
395
|
* These functions provide HTTP capabilities that bypass browser CORS
|
|
244
396
|
* restrictions by using Rust's HTTP client under the hood.
|
|
397
|
+
*
|
|
398
|
+
* Project path for downloads is auto-detected from the runtime context.
|
|
245
399
|
*/
|
|
246
400
|
/**
|
|
247
401
|
* Fetch a URL using Rust's HTTP client (bypasses CORS)
|
|
@@ -285,18 +439,18 @@ async function fetchUrl(url, options = {}) {
|
|
|
285
439
|
* the binary data through JavaScript. The filename is derived from
|
|
286
440
|
* the URL, and file extension is inferred from Content-Type if needed.
|
|
287
441
|
*
|
|
442
|
+
* Project path is auto-detected from the runtime context.
|
|
443
|
+
*
|
|
288
444
|
* @param url - URL to download
|
|
289
|
-
* @param projectPath - Absolute path to the project directory
|
|
290
445
|
* @param targetDir - Target directory within project (e.g., "assets")
|
|
291
446
|
* @param options - Optional download configuration
|
|
292
447
|
* @returns Download result with actual path where file was saved
|
|
293
|
-
* @throws Error if download or write fails
|
|
448
|
+
* @throws Error if download or write fails, or called outside a hook
|
|
294
449
|
*
|
|
295
450
|
* @example
|
|
296
451
|
* ```typescript
|
|
297
452
|
* const result = await downloadAsset(
|
|
298
453
|
* "https://example.com/image",
|
|
299
|
-
* "/path/to/project",
|
|
300
454
|
* "assets"
|
|
301
455
|
* );
|
|
302
456
|
* if (result.ok) {
|
|
@@ -304,11 +458,12 @@ async function fetchUrl(url, options = {}) {
|
|
|
304
458
|
* }
|
|
305
459
|
* ```
|
|
306
460
|
*/
|
|
307
|
-
async function downloadAsset(url,
|
|
461
|
+
async function downloadAsset(url, targetDir, options = {}) {
|
|
462
|
+
const ctx = getInternalContext();
|
|
308
463
|
const { timeoutMs = 3e4 } = options;
|
|
309
464
|
const result = await getTauriCore().invoke("download_asset", {
|
|
310
465
|
url,
|
|
311
|
-
projectPath,
|
|
466
|
+
projectPath: ctx.project_path,
|
|
312
467
|
targetDir,
|
|
313
468
|
timeoutMs
|
|
314
469
|
});
|
|
@@ -328,13 +483,19 @@ async function downloadAsset(url, projectPath, targetDir, options = {}) {
|
|
|
328
483
|
*
|
|
329
484
|
* Allows plugins to execute external binaries (git, npm, etc.)
|
|
330
485
|
* in a controlled environment.
|
|
486
|
+
*
|
|
487
|
+
* Working directory is auto-detected from the runtime context
|
|
488
|
+
* (always the project root).
|
|
331
489
|
*/
|
|
332
490
|
/**
|
|
333
491
|
* Execute an external binary
|
|
334
492
|
*
|
|
335
|
-
*
|
|
493
|
+
* Working directory is auto-detected from the runtime context
|
|
494
|
+
* (always the project root).
|
|
495
|
+
*
|
|
496
|
+
* @param options - Execution options including binary path and args
|
|
336
497
|
* @returns Execution result with stdout, stderr, and exit code
|
|
337
|
-
* @throws Error if binary cannot be executed
|
|
498
|
+
* @throws Error if binary cannot be executed or called outside a hook
|
|
338
499
|
*
|
|
339
500
|
* @example
|
|
340
501
|
* ```typescript
|
|
@@ -342,7 +503,6 @@ async function downloadAsset(url, projectPath, targetDir, options = {}) {
|
|
|
342
503
|
* const result = await executeBinary({
|
|
343
504
|
* binaryPath: "git",
|
|
344
505
|
* args: ["status"],
|
|
345
|
-
* workingDir: "/path/to/repo",
|
|
346
506
|
* });
|
|
347
507
|
*
|
|
348
508
|
* if (result.success) {
|
|
@@ -358,18 +518,18 @@ async function downloadAsset(url, projectPath, targetDir, options = {}) {
|
|
|
358
518
|
* const result = await executeBinary({
|
|
359
519
|
* binaryPath: "npm",
|
|
360
520
|
* args: ["install"],
|
|
361
|
-
* workingDir: "/path/to/project",
|
|
362
521
|
* timeoutMs: 120000,
|
|
363
522
|
* env: { NODE_ENV: "production" },
|
|
364
523
|
* });
|
|
365
524
|
* ```
|
|
366
525
|
*/
|
|
367
526
|
async function executeBinary(options) {
|
|
368
|
-
const
|
|
527
|
+
const ctx = getInternalContext();
|
|
528
|
+
const { binaryPath, args, timeoutMs = 6e4, env } = options;
|
|
369
529
|
const result = await getTauriCore().invoke("execute_binary", {
|
|
370
530
|
binaryPath,
|
|
371
531
|
args,
|
|
372
|
-
workingDir,
|
|
532
|
+
workingDir: ctx.project_path,
|
|
373
533
|
timeoutMs,
|
|
374
534
|
env
|
|
375
535
|
});
|
|
@@ -414,11 +574,10 @@ async function executeBinary(options) {
|
|
|
414
574
|
* ```
|
|
415
575
|
*/
|
|
416
576
|
async function getPluginCookie() {
|
|
417
|
-
const
|
|
418
|
-
if (!context) throw new Error("getPluginCookie() must be called from within a plugin hook. Ensure you're calling this from process(), generate(), deploy(), or syndicate().");
|
|
577
|
+
const ctx = getInternalContext();
|
|
419
578
|
return getTauriCore().invoke("get_plugin_cookie", {
|
|
420
|
-
pluginName:
|
|
421
|
-
projectPath:
|
|
579
|
+
pluginName: ctx.plugin_name,
|
|
580
|
+
projectPath: ctx.project_path
|
|
422
581
|
});
|
|
423
582
|
}
|
|
424
583
|
/**
|
|
@@ -440,15 +599,14 @@ async function getPluginCookie() {
|
|
|
440
599
|
* ```
|
|
441
600
|
*/
|
|
442
601
|
async function setPluginCookie(cookies) {
|
|
443
|
-
const
|
|
444
|
-
if (!context) throw new Error("setPluginCookie() must be called from within a plugin hook. Ensure you're calling this from process(), generate(), deploy(), or syndicate().");
|
|
602
|
+
const ctx = getInternalContext();
|
|
445
603
|
await getTauriCore().invoke("set_plugin_cookie", {
|
|
446
|
-
pluginName:
|
|
447
|
-
projectPath:
|
|
604
|
+
pluginName: ctx.plugin_name,
|
|
605
|
+
projectPath: ctx.project_path,
|
|
448
606
|
cookies
|
|
449
607
|
});
|
|
450
608
|
}
|
|
451
609
|
|
|
452
610
|
//#endregion
|
|
453
|
-
export { closeBrowser, downloadAsset, error, executeBinary, fetchUrl, fileExists, getMessageContext, getPluginCookie, getTauriCore, isTauriAvailable, listFiles, log, openBrowser, readFile, reportComplete, reportError, reportProgress, sendMessage, setMessageContext, setPluginCookie, warn, writeFile };
|
|
611
|
+
export { closeBrowser, downloadAsset, error, executeBinary, fetchUrl, fileExists, getMessageContext, getPluginCookie, getTauriCore, isTauriAvailable, listFiles, listPluginFiles, log, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, sendMessage, setMessageContext, setPluginCookie, warn, writeFile, writePluginFile };
|
|
454
612
|
//# 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/filesystem.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 * File system operations for Moss plugins\n *\n * These functions abstract away the underlying Tauri commands,\n * providing a clean API for plugins to read/write project files.\n */\n\nimport { getTauriCore } from \"./tauri\";\n\n/**\n * Read a file from the project directory\n *\n * @param projectPath - Absolute path to the project directory\n * @param relativePath - Path relative to the project root\n * @returns File contents as a string\n * @throws Error if file cannot be read\n *\n * @example\n * ```typescript\n * const content = await readFile(\"/path/to/project\", \"src/index.ts\");\n * ```\n */\nexport async function readFile(\n projectPath: string,\n relativePath: string\n): Promise<string> {\n return getTauriCore().invoke<string>(\"read_project_file\", {\n projectPath,\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 *\n * @param projectPath - Absolute path to the project directory\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\n *\n * @example\n * ```typescript\n * await writeFile(\"/path/to/project\", \"output/result.md\", \"# Hello World\");\n * ```\n */\nexport async function writeFile(\n projectPath: string,\n relativePath: string,\n content: string\n): Promise<void> {\n await getTauriCore().invoke(\"write_project_file\", {\n projectPath,\n relativePath,\n data: content,\n });\n}\n\n/**\n * List all files in a project directory\n *\n * Returns file paths relative to the project root.\n *\n * @param projectPath - Absolute path to the project directory\n * @returns Array of relative file paths\n * @throws Error if directory cannot be listed\n *\n * @example\n * ```typescript\n * const files = await listFiles(\"/path/to/project\");\n * // [\"src/index.ts\", \"package.json\", \"README.md\"]\n * ```\n */\nexport async function listFiles(projectPath: string): Promise<string[]> {\n return getTauriCore().invoke<string[]>(\"list_project_files\", {\n projectPath,\n });\n}\n\n/**\n * Check if a file exists in the project directory\n *\n * @param projectPath - Absolute path to the project directory\n * @param relativePath - Path relative to the project root\n * @returns true if file exists, false otherwise\n *\n * @example\n * ```typescript\n * if (await fileExists(\"/path/to/project\", \"config.json\")) {\n * // load config\n * }\n * ```\n */\nexport async function fileExists(\n projectPath: string,\n relativePath: string\n): Promise<boolean> {\n try {\n await readFile(projectPath, relativePath);\n return true;\n } catch {\n return false;\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\nimport { getTauriCore } from \"./tauri\";\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 * @param url - URL to download\n * @param projectPath - Absolute path to the project directory\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\n *\n * @example\n * ```typescript\n * const result = await downloadAsset(\n * \"https://example.com/image\",\n * \"/path/to/project\",\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 projectPath: string,\n targetDir: string,\n options: DownloadOptions = {}\n): Promise<DownloadResult> {\n const { timeoutMs = 30000 } = options;\n\n const result = await getTauriCore().invoke<TauriDownloadResult>(\n \"download_asset\",\n {\n url,\n projectPath,\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\nimport { getTauriCore } from \"./tauri\";\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 /** Working directory for execution */\n workingDir: 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 * @param options - Execution options including binary path, args, and working directory\n * @returns Execution result with stdout, stderr, and exit code\n * @throws Error if binary cannot be executed\n *\n * @example\n * ```typescript\n * // Run git status\n * const result = await executeBinary({\n * binaryPath: \"git\",\n * args: [\"status\"],\n * workingDir: \"/path/to/repo\",\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 * workingDir: \"/path/to/project\",\n * timeoutMs: 120000,\n * env: { NODE_ENV: \"production\" },\n * });\n * ```\n */\nexport async function executeBinary(\n options: ExecuteOptions\n): Promise<ExecuteResult> {\n const { binaryPath, args, workingDir, timeoutMs = 60000, env } = options;\n\n const result = await getTauriCore().invoke<TauriBinaryResult>(\n \"execute_binary\",\n {\n binaryPath,\n args,\n workingDir,\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\";\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 * Plugin execution context - set by plugin runtime during hook execution.\n * Used for auto-detecting the calling plugin's identity.\n */\ninterface PluginContext {\n plugin_name: string;\n project_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 context = (window as unknown as { __MOSS_CURRENT_CONTEXT__?: PluginContext }).__MOSS_CURRENT_CONTEXT__;\n\n if (!context) {\n throw new Error(\n \"getPluginCookie() 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 getTauriCore().invoke<Cookie[]>(\"get_plugin_cookie\", {\n pluginName: context.plugin_name,\n projectPath: context.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 context = (window as unknown as { __MOSS_CURRENT_CONTEXT__?: PluginContext }).__MOSS_CURRENT_CONTEXT__;\n\n if (!context) {\n throw new Error(\n \"setPluginCookie() must be called from within a plugin hook. \" +\n \"Ensure you're calling this from process(), generate(), deploy(), or syndicate().\"\n );\n }\n\n await getTauriCore().invoke(\"set_plugin_cookie\", {\n pluginName: context.plugin_name,\n projectPath: context.project_path,\n cookies,\n });\n}\n"],"mappings":";;;;;;;;;;;;AAyBA,SAAgB,eAA0B;CACxC,MAAM,IAAI;AACV,KAAI,CAAC,EAAE,WAAW,KAChB,OAAM,IAAI,MAAM,2BAA2B;AAE7C,QAAO,EAAE,UAAU;;;;;AAMrB,SAAgB,mBAA4B;AAE1C,QAAO,CAAC,CADE,OACC,WAAW;;;;;AC/BxB,IAAI,oBAAoB;AACxB,IAAI,kBAAkB;;;;;AAMtB,SAAgB,kBAAkB,YAAoB,UAAwB;AAC5E,qBAAoB;AACpB,mBAAkB;;;;;AAMpB,SAAgB,oBAA8D;AAC5E,QAAO;EAAE,YAAY;EAAmB,UAAU;EAAiB;;;;;;AAOrE,eAAsB,YAAY,SAAuC;AACvE,KAAI,CAAC,kBAAkB,CACrB;AAGF,KAAI;AACF,QAAM,cAAc,CAAC,OAAO,kBAAkB;GAC5C,YAAY;GACZ,UAAU;GACV;GACD,CAAC;SACI;;;;;AAQV,eAAsB,eACpB,OACA,SACA,OACA,SACe;AACf,OAAM,YAAY;EAAE,MAAM;EAAY;EAAO;EAAS;EAAO;EAAS,CAAC;;;;;AAMzE,eAAsB,YACpB,SACA,SACA,QAAQ,OACO;AACf,OAAM,YAAY;EAAE,MAAM;EAAS;EAAO;EAAS;EAAO,CAAC;;;;;AAM7D,eAAsB,eAAe,QAAgC;AACnE,OAAM,YAAY;EAAE,MAAM;EAAY;EAAQ,CAAC;;;;;;;;;;;AChEjD,eAAsB,IAAI,SAAgC;AACxD,OAAM,YAAY;EAAE,MAAM;EAAO,OAAO;EAAO;EAAS,CAAC;;;;;AAM3D,eAAsB,KAAK,SAAgC;AACzD,OAAM,YAAY;EAAE,MAAM;EAAO,OAAO;EAAQ;EAAS,CAAC;;;;;AAM5D,eAAsB,MAAM,SAAgC;AAC1D,OAAM,YAAY;EAAE,MAAM;EAAO,OAAO;EAAS;EAAS,CAAC;;;;;;;;;;;;ACd7D,eAAsB,YAAY,KAA4B;AAC5D,OAAM,cAAc,CAAC,OAAO,uBAAuB,EAAE,KAAK,CAAC;;;;;AAM7D,eAAsB,eAA8B;AAClD,OAAM,cAAc,CAAC,OAAO,wBAAwB,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;ACIzD,eAAsB,SACpB,aACA,cACiB;AACjB,QAAO,cAAc,CAAC,OAAe,qBAAqB;EACxD;EACA;EACD,CAAC;;;;;;;;;;;;;;;;;AAkBJ,eAAsB,UACpB,aACA,cACA,SACe;AACf,OAAM,cAAc,CAAC,OAAO,sBAAsB;EAChD;EACA;EACA,MAAM;EACP,CAAC;;;;;;;;;;;;;;;;;AAkBJ,eAAsB,UAAU,aAAwC;AACtE,QAAO,cAAc,CAAC,OAAiB,sBAAsB,EAC3D,aACD,CAAC;;;;;;;;;;;;;;;;AAiBJ,eAAsB,WACpB,aACA,cACkB;AAClB,KAAI;AACF,QAAM,SAAS,aAAa,aAAa;AACzC,SAAO;SACD;AACN,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACFX,eAAsB,SACpB,KACA,UAAwB,EAAE,EACJ;CACtB,MAAM,EAAE,YAAY,QAAU;CAE9B,MAAM,SAAS,MAAM,cAAc,CAAC,OAAyB,aAAa;EACxE;EACA;EACD,CAAC;CAGF,MAAM,eAAe,KAAK,OAAO,YAAY;CAC7C,MAAM,QAAQ,IAAI,WAAW,aAAa,OAAO;AACjD,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,IACvC,OAAM,KAAK,aAAa,WAAW,EAAE;AAGvC,QAAO;EACL,QAAQ,OAAO;EACf,IAAI,OAAO;EACX,aAAa,OAAO;EACpB,MAAM;EACN,OAAe;AACb,UAAO,IAAI,aAAa,CAAC,OAAO,MAAM;;EAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BH,eAAsB,cACpB,KACA,aACA,WACA,UAA2B,EAAE,EACJ;CACzB,MAAM,EAAE,YAAY,QAAU;CAE9B,MAAM,SAAS,MAAM,cAAc,CAAC,OAClC,kBACA;EACE;EACA;EACA;EACA;EACD,CACF;AAED,QAAO;EACL,QAAQ,OAAO;EACf,IAAI,OAAO;EACX,aAAa,OAAO;EACpB,cAAc,OAAO;EACrB,YAAY,OAAO;EACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACtFH,eAAsB,cACpB,SACwB;CACxB,MAAM,EAAE,YAAY,MAAM,YAAY,YAAY,KAAO,QAAQ;CAEjE,MAAM,SAAS,MAAM,cAAc,CAAC,OAClC,kBACA;EACE;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QAAO;EACL,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,QAAQ,OAAO;EAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClDH,eAAsB,kBAAqC;CACzD,MAAM,UAAW,OAAmE;AAEpF,KAAI,CAAC,QACH,OAAM,IAAI,MACR,+IAED;AAGH,QAAO,cAAc,CAAC,OAAiB,qBAAqB;EAC1D,YAAY,QAAQ;EACpB,aAAa,QAAQ;EACtB,CAAC;;;;;;;;;;;;;;;;;;;;AAqBJ,eAAsB,gBAAgB,SAAkC;CACtE,MAAM,UAAW,OAAmE;AAEpF,KAAI,CAAC,QACH,OAAM,IAAI,MACR,+IAED;AAGH,OAAM,cAAc,CAAC,OAAO,qBAAqB;EAC/C,YAAY,QAAQ;EACpB,aAAa,QAAQ;EACrB;EACD,CAAC"}
|
|
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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"}
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -198,6 +198,15 @@ interface MockBrowserTracker {
|
|
|
198
198
|
* Create a new browser tracker instance
|
|
199
199
|
*/
|
|
200
200
|
declare function createMockBrowserTracker(): MockBrowserTracker;
|
|
201
|
+
/**
|
|
202
|
+
* Options for setting up mock Tauri environment
|
|
203
|
+
*/
|
|
204
|
+
interface SetupMockTauriOptions {
|
|
205
|
+
/** Plugin name for internal context (default: "test-plugin") */
|
|
206
|
+
pluginName?: string;
|
|
207
|
+
/** Project path for internal context (default: "/test/project") */
|
|
208
|
+
projectPath?: string;
|
|
209
|
+
}
|
|
201
210
|
/**
|
|
202
211
|
* Context returned by setupMockTauri with all mock utilities
|
|
203
212
|
*/
|
|
@@ -214,6 +223,10 @@ interface MockTauriContext {
|
|
|
214
223
|
cookieStorage: MockCookieStorage;
|
|
215
224
|
/** Browser open/close tracking */
|
|
216
225
|
browserTracker: MockBrowserTracker;
|
|
226
|
+
/** The project path used for internal context */
|
|
227
|
+
projectPath: string;
|
|
228
|
+
/** The plugin name used for internal context */
|
|
229
|
+
pluginName: string;
|
|
217
230
|
/** Cleanup function - must be called after tests */
|
|
218
231
|
cleanup: () => void;
|
|
219
232
|
}
|
|
@@ -221,16 +234,18 @@ interface MockTauriContext {
|
|
|
221
234
|
* Set up mock Tauri IPC for testing
|
|
222
235
|
*
|
|
223
236
|
* This sets up `window.__TAURI__.core.invoke` to intercept all IPC calls
|
|
224
|
-
* and route them to in-memory implementations.
|
|
237
|
+
* and route them to in-memory implementations. It also sets up
|
|
238
|
+
* `__MOSS_INTERNAL_CONTEXT__` for the context-aware APIs.
|
|
225
239
|
*
|
|
240
|
+
* @param options - Optional configuration for project path and plugin name
|
|
226
241
|
* @returns Context with mock utilities and cleanup function
|
|
227
242
|
*
|
|
228
243
|
* @example
|
|
229
244
|
* ```typescript
|
|
230
|
-
* const ctx = setupMockTauri();
|
|
245
|
+
* const ctx = setupMockTauri({ projectPath: "/my/project", pluginName: "my-plugin" });
|
|
231
246
|
*
|
|
232
247
|
* // Set up test data
|
|
233
|
-
* ctx.filesystem.setFile("/project/article.md", "# Test");
|
|
248
|
+
* ctx.filesystem.setFile("/my/project/article.md", "# Test");
|
|
234
249
|
* ctx.urlConfig.setResponse("https://example.com/image.png", {
|
|
235
250
|
* status: 200,
|
|
236
251
|
* ok: true,
|
|
@@ -247,7 +262,7 @@ interface MockTauriContext {
|
|
|
247
262
|
* ctx.cleanup();
|
|
248
263
|
* ```
|
|
249
264
|
*/
|
|
250
|
-
declare function setupMockTauri(): MockTauriContext;
|
|
265
|
+
declare function setupMockTauri(options?: SetupMockTauriOptions): MockTauriContext;
|
|
251
266
|
//#endregion
|
|
252
|
-
export { type DownloadTracker, type MockBinaryConfig, type MockBinaryResult, type MockBrowserTracker, type MockCookieStorage, type MockFile, type MockFilesystem, type MockTauriContext, type MockUrlConfig, type MockUrlResponse, createDownloadTracker, createMockBinaryConfig, createMockBrowserTracker, createMockCookieStorage, createMockFilesystem, createMockUrlConfig, setupMockTauri };
|
|
267
|
+
export { type DownloadTracker, type MockBinaryConfig, type MockBinaryResult, type MockBrowserTracker, type MockCookieStorage, type MockFile, type MockFilesystem, type MockTauriContext, type MockUrlConfig, type MockUrlResponse, type SetupMockTauriOptions, createDownloadTracker, createMockBinaryConfig, createMockBrowserTracker, createMockCookieStorage, createMockFilesystem, createMockUrlConfig, setupMockTauri };
|
|
253
268
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -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;AA8DA;
|
|
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;AA8DA;AAUiB,UAraA,cAAA,CAqagB;EAEnB;EAEK,KAAA,EAvaV,GAuaU,CAAA,MAAA,EAvaE,QAuaF,CAAA;EAEN;EAEG,OAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAzaS,QAyaT,GAAA,SAAA;EAEC;EAEC,OAAA,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAkB;EAyCpB,UAAA,CAAA,IAAA,EAAc,MAAA,CAAA,EAAA,OAAW;;;;;;;;;iBAxczB,oBAAA,CAAA,GAAwB;;;;UA0CvB,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;;;;UA8D3B,qBAAA;;;;;;;;;UAUA,gBAAA;;cAEH;;mBAEK;;aAEN;;gBAEG;;iBAEC;;kBAEC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCF,cAAA,WAAyB,wBAAwB"}
|
package/dist/testing/index.mjs
CHANGED
|
@@ -199,16 +199,18 @@ function extractFilenameFromUrl(url) {
|
|
|
199
199
|
* Set up mock Tauri IPC for testing
|
|
200
200
|
*
|
|
201
201
|
* This sets up `window.__TAURI__.core.invoke` to intercept all IPC calls
|
|
202
|
-
* and route them to in-memory implementations.
|
|
202
|
+
* and route them to in-memory implementations. It also sets up
|
|
203
|
+
* `__MOSS_INTERNAL_CONTEXT__` for the context-aware APIs.
|
|
203
204
|
*
|
|
205
|
+
* @param options - Optional configuration for project path and plugin name
|
|
204
206
|
* @returns Context with mock utilities and cleanup function
|
|
205
207
|
*
|
|
206
208
|
* @example
|
|
207
209
|
* ```typescript
|
|
208
|
-
* const ctx = setupMockTauri();
|
|
210
|
+
* const ctx = setupMockTauri({ projectPath: "/my/project", pluginName: "my-plugin" });
|
|
209
211
|
*
|
|
210
212
|
* // Set up test data
|
|
211
|
-
* ctx.filesystem.setFile("/project/article.md", "# Test");
|
|
213
|
+
* ctx.filesystem.setFile("/my/project/article.md", "# Test");
|
|
212
214
|
* ctx.urlConfig.setResponse("https://example.com/image.png", {
|
|
213
215
|
* status: 200,
|
|
214
216
|
* ok: true,
|
|
@@ -225,7 +227,9 @@ function extractFilenameFromUrl(url) {
|
|
|
225
227
|
* ctx.cleanup();
|
|
226
228
|
* ```
|
|
227
229
|
*/
|
|
228
|
-
function setupMockTauri() {
|
|
230
|
+
function setupMockTauri(options) {
|
|
231
|
+
const projectPath = options?.projectPath ?? "/test/project";
|
|
232
|
+
const pluginName = options?.pluginName ?? "test-plugin";
|
|
229
233
|
const filesystem = createMockFilesystem();
|
|
230
234
|
const downloadTracker = createDownloadTracker();
|
|
231
235
|
const urlConfig = createMockUrlConfig();
|
|
@@ -242,16 +246,16 @@ function setupMockTauri() {
|
|
|
242
246
|
throw new Error(`File not found: ${fullPath}`);
|
|
243
247
|
}
|
|
244
248
|
case "write_project_file": {
|
|
245
|
-
const projectPath = payload?.projectPath;
|
|
249
|
+
const projectPath$1 = payload?.projectPath;
|
|
246
250
|
const relativePath = payload?.relativePath;
|
|
247
251
|
const content = payload?.data;
|
|
248
|
-
const fullPath = `${projectPath}/${relativePath}`;
|
|
252
|
+
const fullPath = `${projectPath$1}/${relativePath}`;
|
|
249
253
|
filesystem.setFile(fullPath, content);
|
|
250
254
|
return null;
|
|
251
255
|
}
|
|
252
256
|
case "list_project_files": {
|
|
253
|
-
const projectPath = payload?.projectPath;
|
|
254
|
-
return filesystem.listFiles().filter((p) => p.startsWith(projectPath + "/")).map((p) => p.substring(projectPath.length + 1));
|
|
257
|
+
const projectPath$1 = payload?.projectPath;
|
|
258
|
+
return filesystem.listFiles().filter((p) => p.startsWith(projectPath$1 + "/")).map((p) => p.substring(projectPath$1.length + 1));
|
|
255
259
|
}
|
|
256
260
|
case "fetch_url": {
|
|
257
261
|
const url = payload?.url;
|
|
@@ -294,15 +298,15 @@ function setupMockTauri() {
|
|
|
294
298
|
return result;
|
|
295
299
|
}
|
|
296
300
|
case "get_plugin_cookie": {
|
|
297
|
-
const pluginName = payload?.pluginName;
|
|
298
|
-
const projectPath = payload?.projectPath;
|
|
299
|
-
return cookieStorage.getCookies(pluginName, projectPath);
|
|
301
|
+
const pluginName$1 = payload?.pluginName;
|
|
302
|
+
const projectPath$1 = payload?.projectPath;
|
|
303
|
+
return cookieStorage.getCookies(pluginName$1, projectPath$1);
|
|
300
304
|
}
|
|
301
305
|
case "set_plugin_cookie": {
|
|
302
|
-
const pluginName = payload?.pluginName;
|
|
303
|
-
const projectPath = payload?.projectPath;
|
|
306
|
+
const pluginName$1 = payload?.pluginName;
|
|
307
|
+
const projectPath$1 = payload?.projectPath;
|
|
304
308
|
const cookies = payload?.cookies;
|
|
305
|
-
cookieStorage.setCookies(pluginName, projectPath, cookies);
|
|
309
|
+
cookieStorage.setCookies(pluginName$1, projectPath$1, cookies);
|
|
306
310
|
return null;
|
|
307
311
|
}
|
|
308
312
|
case "open_plugin_browser": {
|
|
@@ -335,6 +339,11 @@ function setupMockTauri() {
|
|
|
335
339
|
if (typeof globalThis.window === "undefined") globalThis.window = {};
|
|
336
340
|
const win = globalThis.window;
|
|
337
341
|
win.__TAURI__ = { core: { invoke } };
|
|
342
|
+
win.__MOSS_INTERNAL_CONTEXT__ = {
|
|
343
|
+
plugin_name: pluginName,
|
|
344
|
+
project_path: projectPath,
|
|
345
|
+
moss_dir: `${projectPath}/.moss`
|
|
346
|
+
};
|
|
338
347
|
return {
|
|
339
348
|
filesystem,
|
|
340
349
|
downloadTracker,
|
|
@@ -342,8 +351,11 @@ function setupMockTauri() {
|
|
|
342
351
|
binaryConfig,
|
|
343
352
|
cookieStorage,
|
|
344
353
|
browserTracker,
|
|
354
|
+
projectPath,
|
|
355
|
+
pluginName,
|
|
345
356
|
cleanup: () => {
|
|
346
357
|
delete win.__TAURI__;
|
|
358
|
+
delete win.__MOSS_INTERNAL_CONTEXT__;
|
|
347
359
|
filesystem.clear();
|
|
348
360
|
downloadTracker.reset();
|
|
349
361
|
urlConfig.reset();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["completedDownloads: string[]","failedDownloads: Array<{ url: string; error: string }>","defaultResponse: MockUrlResponse","defaultResult: MockBinaryResult","openedUrls: string[]"],"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 * 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 /** 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.\n *\n * @returns Context with mock utilities and cleanup function\n *\n * @example\n * ```typescript\n * const ctx = setupMockTauri();\n *\n * // Set up test data\n * ctx.filesystem.setFile(\"/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(): MockTauriContext {\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 // ======================================================================\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 };\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 { window: { __TAURI__?: { core?: { invoke: typeof invoke } } } }).window;\n win.__TAURI__ = {\n core: { invoke },\n };\n\n return {\n filesystem,\n downloadTracker,\n urlConfig,\n binaryConfig,\n cookieStorage,\n browserTracker,\n cleanup: () => {\n // Clear the mock Tauri interface\n delete win.__TAURI__;\n filesystem.clear();\n downloadTracker.reset();\n urlConfig.reset();\n binaryConfig.reset();\n cookieStorage.clear();\n browserTracker.reset();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["completedDownloads: string[]","failedDownloads: Array<{ url: string; error: string }>","defaultResponse: MockUrlResponse","defaultResult: MockBinaryResult","openedUrls: string[]","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 // ======================================================================\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":"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"}
|