@symbiosis-lab/moss-plugin-matters 1.4.2
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/CHANGELOG.md +88 -0
- package/README.md +18 -0
- package/assets/icon.svg +1 -0
- package/assets/manifest.json +36 -0
- package/codegen.ts +26 -0
- package/e2e/moss-cli.test.ts +338 -0
- package/features/api/fetch-articles.feature +39 -0
- package/features/auth/wallet-auth.feature +27 -0
- package/features/download/retry-logic.feature +36 -0
- package/features/download/self-correcting.feature +83 -0
- package/features/download/worker-pool.feature +29 -0
- package/features/social/fetch-social-data.feature +40 -0
- package/features/steps/api.steps.ts +180 -0
- package/features/steps/download.steps.ts +423 -0
- package/features/steps/incremental-sync.steps.ts +105 -0
- package/features/steps/self-correcting.steps.ts +575 -0
- package/features/steps/social.steps.ts +257 -0
- package/features/steps/syndication.steps.ts +264 -0
- package/features/steps/wallet-auth.steps.ts +185 -0
- package/features/sync/article-sync.feature +49 -0
- package/features/sync/homepage-grid.feature +43 -0
- package/features/sync/incremental-sync.feature +28 -0
- package/features/syndication/create-draft.feature +35 -0
- package/package.json +58 -0
- package/src/__generated__/schema.graphql +4289 -0
- package/src/__generated__/types.ts +5355 -0
- package/src/__tests__/api.test.ts +678 -0
- package/src/__tests__/auth-route.test.ts +38 -0
- package/src/__tests__/auth-routing.test.ts +462 -0
- package/src/__tests__/auto-detect.test.ts +412 -0
- package/src/__tests__/binding-guard.test.ts +256 -0
- package/src/__tests__/config.test.ts +212 -0
- package/src/__tests__/converter.test.ts +289 -0
- package/src/__tests__/credential.test.ts +332 -0
- package/src/__tests__/domain.test.ts +341 -0
- package/src/__tests__/downloader.test.ts +679 -0
- package/src/__tests__/folder-detection.test.ts +289 -0
- package/src/__tests__/force-fresh-login.test.ts +236 -0
- package/src/__tests__/main.test.ts +2437 -0
- package/src/__tests__/progress.test.ts +93 -0
- package/src/__tests__/session.test.ts +375 -0
- package/src/__tests__/social-integration.test.ts +386 -0
- package/src/__tests__/social-sync-logic.test.ts +107 -0
- package/src/__tests__/social.test.ts +788 -0
- package/src/__tests__/sync.test.ts +1273 -0
- package/src/__tests__/syndication-toast-law.test.ts +649 -0
- package/src/__tests__/syndication.test.ts +125 -0
- package/src/__tests__/test-profile-escape.test.ts +209 -0
- package/src/__tests__/url-detect.test.ts +79 -0
- package/src/__tests__/utils.test.ts +226 -0
- package/src/api.ts +1366 -0
- package/src/auth-route.ts +38 -0
- package/src/config.ts +80 -0
- package/src/converter.ts +305 -0
- package/src/credential.ts +329 -0
- package/src/domain.ts +183 -0
- package/src/downloader.ts +761 -0
- package/src/main.ts +2092 -0
- package/src/progress.ts +89 -0
- package/src/queries/user.graphql +85 -0
- package/src/queries/viewer.graphql +104 -0
- package/src/social.ts +413 -0
- package/src/sync.ts +818 -0
- package/src/types.ts +477 -0
- package/src/url-detect.ts +49 -0
- package/src/utils.ts +305 -0
- package/test-fixtures/syndication-test-site/input/index.md +8 -0
- package/test-fixtures/syndication-test-site/input/posts/rich-test-article.md +90 -0
- package/test-helpers/TEST_ACCOUNT.md +151 -0
- package/test-helpers/api-client.ts +252 -0
- package/test-helpers/fixtures/articles.ts +147 -0
- package/test-helpers/wallet-auth.ts +305 -0
- package/test-setup/e2e.ts +93 -0
- package/tsconfig.json +23 -0
- package/vitest.config.ts +39 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#738](https://github.com/Symbiosis-Lab/moss/pull/738) [`8539776`](https://github.com/Symbiosis-Lab/moss/commit/853977618a92b5d66853be8ca9558012b45183e5) Thanks [@guoliu](https://github.com/guoliu)! - First publish of the github and matters moss plugins to npm under the @symbiosis-lab scope. Sources consolidated into the moss monorepo; published from the changesets workflow. (The five other plugins originally listed here — douban, linkedin, substack, x, xiaohongshu — do not yet exist as packages and were removed so `changeset version` can resolve.)
|
|
8
|
+
|
|
9
|
+
All notable changes to this plugin are documented in this file.
|
|
10
|
+
|
|
11
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
_Pending publish — cumulative since `1.1.2` (last released on main); full detail under [1.4.0] and [1.2.0]._
|
|
16
|
+
|
|
17
|
+
- Changed (`1.4.1`): the login flow is quieter and recoverable — a cancelled or failed Matters login now returns you to the editor (empty-folder onboarding) instead of leaving an empty action panel, the login status label reads calmer, and the in-flight watchdog is preserved across the cancel.
|
|
18
|
+
- Fixed (`1.4.1`): a freshly imported vault's homepage title comes from the vault folder name, not the Matters display name.
|
|
19
|
+
- Fixed: the import progress bar no longer stalls during media download. Per-article sync and image-download progress now drive the unified progress surface, so the hairline advances smoothly through the heaviest phase. (These previously used a legacy progress channel that moss's panel drops for background imports, so the bar appeared frozen while images downloaded.)
|
|
20
|
+
- Changed: the sync receipt now leads with a noun and reads as one fact — e.g. "12 articles already up to date" instead of a bare, truncated "5 unchanged, images: 1 failed, 0 new comments". Image/link/comment outcomes no longer clutter it (zero-count clauses dropped), and a failed image download is surfaced as its own advisory carrying the image's **URL** (the dead CDN reference still in the body) so you can see which image broke, rather than an opaque "1 failed" count.
|
|
21
|
+
- Fixed: section headings no longer show a stray `#` on Matters. moss appends a hover-only `<a class="moss-heading-anchor">#</a>` permalink to every heading; Matters' sanitizer kept the `#` text, so headings synced as e.g. "1.#". The anchor (web-only chrome) is now stripped during syndication. Verified against `server.matters.icu`.
|
|
22
|
+
- Fixed: comments now download for articles syndicated with a Matters **short-link** URL (`https://matters.town/a/<shortHash>`), not only the canonical `https://matters.town/@user/<slug>-<shortHash>` form. Previously `extractShortHash` required a hyphen in the final path segment, so short-link articles were silently dropped from `scanLocalArticles` and never fetched comments. The two duplicate `extractShortHash` implementations (sync + downloader) are now one shared function in `domain.ts` that understands both forms; an unparseable syndicated URL is now logged instead of dropped silently.
|
|
23
|
+
- Fixed: images, covers, and audio now upload to Matters correctly. Assets are uploaded by **bytes** read from the local build output (multipart `singleFileUpload`), not by URL. Matters' server cannot reliably fetch assets by URL from a deployed site (Caddy/moss-seta hosts return `UNABLE_TO_UPLOAD_FROM_URL`), and `embedaudio` rejects url-upload entirely — so previously images often broke and audio never uploaded. On upload failure, image/audio srcs fall back to the absolutized deployed URL so they still display. Verified end-to-end against `server.matters.icu`.
|
|
24
|
+
- Fixed: audio embeds now syndicate to Matters at all. moss's `<audio class="moss-embed">` is rewritten into Matters' required `<figure class="audio">` shape (the only shape its sanitizer keeps; previously the entire `<audio>` was stripped to stray fallback text), then the audio bytes are uploaded via `embedaudio`.
|
|
25
|
+
- Local-first comments: `uid` contract, env-derived Artalk server URL, tombstone reconcile, morph-proof preview stub.
|
|
26
|
+
- Social data written to `.moss/data/social/`; stranded-comment recovery.
|
|
27
|
+
- Session-expiry handling: JWT decode, tri-state session, dead-token filter, trigger-aware auth routing, honest receipts.
|
|
28
|
+
|
|
29
|
+
## [1.4.0] - 2026-06-11
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Local-first comments integration: `uid` contract, env-derived Artalk server URL,
|
|
34
|
+
tombstone reconcile, morph-proof preview stub.
|
|
35
|
+
- Social data written to `.moss/data/social/` alongside build; recovers stranded comments.
|
|
36
|
+
- Diagnostic advisories on hook-failure with full refetch on cleared counts.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Full refetch on cleared platform counts to avoid stale display.
|
|
41
|
+
|
|
42
|
+
## [1.2.0] - 2026-06-04
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- Session-expiry handling across 7 implementation tasks (T1–T7):
|
|
47
|
+
- Decode JWT `exp` claim locally (T1).
|
|
48
|
+
- Tri-state session check with dead-token filtering and persisted nudge stamp (T2).
|
|
49
|
+
- Typed `MattersAuthError` from response bodies (T3).
|
|
50
|
+
- Pure trigger-aware auth router (T4).
|
|
51
|
+
- Trigger-aware auth routing, gated binding guard, honest receipts (T5).
|
|
52
|
+
- Mid-sync auth failure copy and tri-state syndicate gate (T6).
|
|
53
|
+
- Review fixes: cookie dead-token filter, `queryMode` reset, receipt copy (T6.5).
|
|
54
|
+
- Reconciled plugin version after rebase (T7).
|
|
55
|
+
- `MOSS_MATTERS_DOMAIN` env var to switch test/prod domain in-webview.
|
|
56
|
+
- `MOSS_MATTERS_TEST_PROFILE` env var bypasses login in test builds.
|
|
57
|
+
- moss-injected trigger context; terminated leaked background tasks.
|
|
58
|
+
|
|
59
|
+
## [1.1.2] - 2026-05-30
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Reconcile plugin manifest after QuickJS runtime upgrade in moss v0.7.x.
|
|
64
|
+
|
|
65
|
+
## [1.1.1] - 2026-05-29
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- Rebuild bundled plugin correctly on release build (`cargo build --release`).
|
|
70
|
+
- Language-aware article folder (文章 for Chinese content).
|
|
71
|
+
- Convert HTML via moss's shared htmd converter.
|
|
72
|
+
- Emit filename-only wikilinks for assets.
|
|
73
|
+
- Trim tag whitespace; localize legacy non-UUID assets.
|
|
74
|
+
- Pre-merge review fixes: G runtime no-op, title wikilink, hairline clip.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- Generate self-named home article with `home: true` marker.
|
|
79
|
+
- Marker-aware home detection exposed to plugin-facing API.
|
|
80
|
+
|
|
81
|
+
## [0.0.2] - 2026-05-29
|
|
82
|
+
|
|
83
|
+
> **Note:** This npm publication was experimental. The plugin version lineage returned to
|
|
84
|
+
> 1.x after the open-source consolidation; `0.0.2` is documented here for history only
|
|
85
|
+
> and was never successfully published to npm.
|
|
86
|
+
|
|
87
|
+
Initial publication attempt under the `@symbiosis-lab` scope, bundled with the
|
|
88
|
+
open-source release pipeline.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @symbiosis-lab/moss-plugin-matters
|
|
2
|
+
|
|
3
|
+
> Publish moss posts to matters.town.
|
|
4
|
+
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](../#stability)
|
|
7
|
+
|
|
8
|
+
> **Read-only mirror.** Source lives in the private moss monorepo. PRs cannot be merged in the mirror — see [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
9
|
+
|
|
10
|
+
A moss publishing plugin for matters.town. See [moss.pub](https://mosspub.com) and the [plugin index](../README.md) for the full plugin lineup.
|
|
11
|
+
|
|
12
|
+
## Stability
|
|
13
|
+
|
|
14
|
+
This plugin is 0.x. APIs may change between minor versions until 1.0. See [CHANGELOG.md](../CHANGELOG.md).
|
|
15
|
+
|
|
16
|
+
## License
|
|
17
|
+
|
|
18
|
+
MIT — see [LICENSE](../LICENSE).
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><svg id="_圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 385.45 265"><defs><style>.cls-1{fill:url(#_未命名漸層_4);}.cls-1,.cls-2{fill-rule:evenodd;}.cls-2{fill:url(#_未命名漸層_3);}</style><linearGradient id="_未命名漸層_4" x1="181.63" y1="-2654.34" x2="358.94" y2="-2793.05" gradientTransform="translate(0 -2587.71) scale(1 -1)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#d7eae1"/><stop offset="1" stop-color="#79b1a6"/></linearGradient><linearGradient id="_未命名漸層_3" x1="62.75" y1="-2647.18" x2="215.96" y2="-2806.64" gradientTransform="translate(0 -2587.71) scale(1 -1)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f4e2bc"/><stop offset="1" stop-color="#bf9f5e"/></linearGradient></defs><path class="cls-1" d="M265.83,251.75c66.07,0,119.62-53.39,119.62-119.25S331.9,13.25,265.83,13.25s-119.62,53.39-119.62,119.25,53.55,119.25,119.62,119.25Z"/><path class="cls-2" d="M132.92,265c73.41,0,132.92-59.32,132.92-132.5S206.33,0,132.92,0,0,59.32,0,132.5s59.5,132.5,132.92,132.5Z"/></svg>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "matters",
|
|
3
|
+
"version": "1.4.2",
|
|
4
|
+
"description": "Syndicate your articles to Matters.town (POSSE)",
|
|
5
|
+
"author": "moss team",
|
|
6
|
+
"entry": "main.bundle.js",
|
|
7
|
+
"capabilities": [
|
|
8
|
+
"process",
|
|
9
|
+
"syndicate",
|
|
10
|
+
"import"
|
|
11
|
+
],
|
|
12
|
+
"global_name": "MattersPlugin",
|
|
13
|
+
"domain": "matters.town",
|
|
14
|
+
"domains": ["matters.town", "matters.icu"],
|
|
15
|
+
"icon": "icon.svg",
|
|
16
|
+
"config": {
|
|
17
|
+
"auto_publish": false,
|
|
18
|
+
"add_canonical_link": true,
|
|
19
|
+
"sync_on_build": true,
|
|
20
|
+
"sync_drafts": true
|
|
21
|
+
},
|
|
22
|
+
"config_schema": {
|
|
23
|
+
"auto_publish": "boolean",
|
|
24
|
+
"add_canonical_link": "boolean",
|
|
25
|
+
"sync_on_build": "boolean",
|
|
26
|
+
"sync_drafts": "boolean"
|
|
27
|
+
},
|
|
28
|
+
"contributes": {
|
|
29
|
+
"jobs": {
|
|
30
|
+
"syndicate": {
|
|
31
|
+
"verb": "Syndicated",
|
|
32
|
+
"noun": "posts"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/codegen.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CodegenConfig } from "@graphql-codegen/cli";
|
|
2
|
+
|
|
3
|
+
const config: CodegenConfig = {
|
|
4
|
+
// Fetch schema via introspection from live endpoint
|
|
5
|
+
schema: "https://server.matters.town/graphql",
|
|
6
|
+
documents: ["src/queries/**/*.graphql"],
|
|
7
|
+
generates: {
|
|
8
|
+
// Generate TypeScript types from schema and operations
|
|
9
|
+
"./src/__generated__/types.ts": {
|
|
10
|
+
plugins: ["typescript", "typescript-operations"],
|
|
11
|
+
config: {
|
|
12
|
+
enumsAsTypes: true,
|
|
13
|
+
preResolveTypes: true,
|
|
14
|
+
skipTypename: true,
|
|
15
|
+
// Use 'Maybe' for nullable fields
|
|
16
|
+
maybeValue: "T | null | undefined",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
// Save introspected schema locally for reference
|
|
20
|
+
"./src/__generated__/schema.graphql": {
|
|
21
|
+
plugins: ["schema-ast"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default config;
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests for Matters Plugin using moss CLI
|
|
3
|
+
*
|
|
4
|
+
* These tests verify the Matters plugin works correctly when invoked through
|
|
5
|
+
* the moss CLI, testing real-world scenarios.
|
|
6
|
+
*
|
|
7
|
+
* The Matters plugin has capabilities:
|
|
8
|
+
* - process: Syncs articles from Matters.town during build
|
|
9
|
+
* - syndicate: Publishes articles to Matters.town after deploy
|
|
10
|
+
*
|
|
11
|
+
* Requirements:
|
|
12
|
+
* - moss binary with --wait-plugins support (v0.3.1+)
|
|
13
|
+
* - Plugin built (npm run build)
|
|
14
|
+
* - Display server available (xvfb-run on Linux CI)
|
|
15
|
+
*
|
|
16
|
+
* Note: Tests verify graceful handling when not authenticated.
|
|
17
|
+
* Full integration tests with real Matters API require authentication.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
21
|
+
import { execSync, spawn } from "child_process";
|
|
22
|
+
import * as fs from "fs";
|
|
23
|
+
import * as path from "path";
|
|
24
|
+
import * as os from "os";
|
|
25
|
+
|
|
26
|
+
// Path to moss binary - check env var first, then fallback to local dev path
|
|
27
|
+
const MOSS_BINARY =
|
|
28
|
+
process.env.MOSS_BINARY ||
|
|
29
|
+
path.join(__dirname, "../../../../moss/develop/src-tauri/target/debug/moss");
|
|
30
|
+
|
|
31
|
+
// Check if --wait-plugins is supported (v0.3.1+)
|
|
32
|
+
let HAS_WAIT_PLUGINS = false;
|
|
33
|
+
|
|
34
|
+
// Path to plugin dist
|
|
35
|
+
const PLUGIN_DIST = path.join(__dirname, "../dist");
|
|
36
|
+
|
|
37
|
+
// Test fixture directory
|
|
38
|
+
let testDir: string;
|
|
39
|
+
let fixtureCounter = 0;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a test fixture directory with optional configuration
|
|
43
|
+
*/
|
|
44
|
+
function createFixture(options: {
|
|
45
|
+
withGit?: boolean;
|
|
46
|
+
withRemote?: string;
|
|
47
|
+
withPlugin?: boolean;
|
|
48
|
+
withMattersConfig?: Record<string, unknown>;
|
|
49
|
+
content?: Record<string, string>;
|
|
50
|
+
}): string {
|
|
51
|
+
const fixtureName = `moss-matters-e2e-${Date.now()}-${fixtureCounter++}`;
|
|
52
|
+
const fixturePath = path.join(testDir, fixtureName);
|
|
53
|
+
fs.mkdirSync(fixturePath, { recursive: true });
|
|
54
|
+
|
|
55
|
+
// Create content files
|
|
56
|
+
const defaultContent = {
|
|
57
|
+
"index.md": "# Hello World\n\nThis is a test site.",
|
|
58
|
+
"posts/article1.md": `---
|
|
59
|
+
title: Test Article
|
|
60
|
+
date: 2024-01-01
|
|
61
|
+
tags:
|
|
62
|
+
- test
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# Test Article
|
|
66
|
+
|
|
67
|
+
This is a test article for Matters plugin testing.
|
|
68
|
+
`,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const content = options.content || defaultContent;
|
|
72
|
+
for (const [filename, fileContent] of Object.entries(content)) {
|
|
73
|
+
const filePath = path.join(fixturePath, filename);
|
|
74
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
75
|
+
fs.writeFileSync(filePath, fileContent);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Initialize git if requested
|
|
79
|
+
if (options.withGit) {
|
|
80
|
+
execSync("git init", { cwd: fixturePath, stdio: "pipe" });
|
|
81
|
+
execSync("git config user.email 'test@example.com'", {
|
|
82
|
+
cwd: fixturePath,
|
|
83
|
+
stdio: "pipe",
|
|
84
|
+
});
|
|
85
|
+
execSync("git config user.name 'Test User'", {
|
|
86
|
+
cwd: fixturePath,
|
|
87
|
+
stdio: "pipe",
|
|
88
|
+
});
|
|
89
|
+
execSync("git add .", { cwd: fixturePath, stdio: "pipe" });
|
|
90
|
+
execSync('git commit -m "Initial commit"', {
|
|
91
|
+
cwd: fixturePath,
|
|
92
|
+
stdio: "pipe",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (options.withRemote) {
|
|
96
|
+
execSync(`git remote add origin ${options.withRemote}`, {
|
|
97
|
+
cwd: fixturePath,
|
|
98
|
+
stdio: "pipe",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Install plugin if requested
|
|
104
|
+
if (options.withPlugin) {
|
|
105
|
+
const mossDir = path.join(fixturePath, ".moss");
|
|
106
|
+
const pluginsDir = path.join(mossDir, "plugins");
|
|
107
|
+
const mattersPluginDir = path.join(pluginsDir, "matters");
|
|
108
|
+
|
|
109
|
+
fs.mkdirSync(mattersPluginDir, { recursive: true });
|
|
110
|
+
|
|
111
|
+
// Copy plugin files
|
|
112
|
+
const pluginFiles = ["main.bundle.js", "manifest.json", "icon.svg"];
|
|
113
|
+
for (const file of pluginFiles) {
|
|
114
|
+
const src = path.join(PLUGIN_DIST, file);
|
|
115
|
+
const dest = path.join(mattersPluginDir, file);
|
|
116
|
+
if (fs.existsSync(src)) {
|
|
117
|
+
fs.copyFileSync(src, dest);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Write plugin config if provided
|
|
122
|
+
if (options.withMattersConfig) {
|
|
123
|
+
const configPath = path.join(mattersPluginDir, "config.json");
|
|
124
|
+
fs.writeFileSync(
|
|
125
|
+
configPath,
|
|
126
|
+
JSON.stringify(options.withMattersConfig, null, 2)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return fixturePath;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Run moss CLI and return stdout/stderr
|
|
136
|
+
*/
|
|
137
|
+
function runMoss(
|
|
138
|
+
args: string[],
|
|
139
|
+
options?: { cwd?: string; timeout?: number }
|
|
140
|
+
): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const proc = spawn(MOSS_BINARY, args, {
|
|
143
|
+
cwd: options?.cwd || testDir,
|
|
144
|
+
timeout: options?.timeout || 30000,
|
|
145
|
+
env: {
|
|
146
|
+
...process.env,
|
|
147
|
+
CI: "true",
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let stdout = "";
|
|
152
|
+
let stderr = "";
|
|
153
|
+
|
|
154
|
+
proc.stdout?.on("data", (data) => {
|
|
155
|
+
stdout += data.toString();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
proc.stderr?.on("data", (data) => {
|
|
159
|
+
stderr += data.toString();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
proc.on("close", (code) => {
|
|
163
|
+
resolve({ stdout, stderr, code: code ?? 1 });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
proc.on("error", (err) => {
|
|
167
|
+
stderr += err.message;
|
|
168
|
+
resolve({ stdout, stderr, code: 1 });
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
describe("Matters Plugin E2E Tests", () => {
|
|
174
|
+
beforeAll(async () => {
|
|
175
|
+
// Verify moss binary exists
|
|
176
|
+
if (!fs.existsSync(MOSS_BINARY)) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`moss binary not found at ${MOSS_BINARY}. ` +
|
|
179
|
+
`Set MOSS_BINARY environment variable or build moss locally.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Verify plugin dist exists
|
|
184
|
+
if (!fs.existsSync(PLUGIN_DIST)) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Plugin dist not found at ${PLUGIN_DIST}. Run 'npm run build' first.`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create temp directory
|
|
191
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), "moss-matters-e2e-"));
|
|
192
|
+
|
|
193
|
+
// Check if --wait-plugins is supported
|
|
194
|
+
const { stdout } = await runMoss(["--help"]);
|
|
195
|
+
HAS_WAIT_PLUGINS = stdout.includes("--wait-plugins");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
afterAll(() => {
|
|
199
|
+
if (testDir && fs.existsSync(testDir)) {
|
|
200
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("Plugin discovery", () => {
|
|
205
|
+
it("has correct manifest structure", () => {
|
|
206
|
+
const fixture = createFixture({ withPlugin: true });
|
|
207
|
+
|
|
208
|
+
const manifestPath = path.join(
|
|
209
|
+
fixture,
|
|
210
|
+
".moss",
|
|
211
|
+
"plugins",
|
|
212
|
+
"matters",
|
|
213
|
+
"manifest.json"
|
|
214
|
+
);
|
|
215
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
216
|
+
|
|
217
|
+
expect(manifest.name).toBe("matters");
|
|
218
|
+
expect(manifest.capabilities).toContain("process");
|
|
219
|
+
expect(manifest.capabilities).toContain("syndicate");
|
|
220
|
+
expect(manifest.domain).toBe("matters.town");
|
|
221
|
+
expect(manifest.entry).toBe("main.bundle.js");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("plugin files are copied correctly", () => {
|
|
225
|
+
const fixture = createFixture({ withPlugin: true });
|
|
226
|
+
|
|
227
|
+
const pluginDir = path.join(fixture, ".moss", "plugins", "matters");
|
|
228
|
+
expect(fs.existsSync(path.join(pluginDir, "manifest.json"))).toBe(true);
|
|
229
|
+
expect(fs.existsSync(path.join(pluginDir, "main.bundle.js"))).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Process Hook Tests
|
|
235
|
+
*
|
|
236
|
+
* The process hook syncs articles from Matters.town.
|
|
237
|
+
* Without authentication, it should gracefully handle the missing auth
|
|
238
|
+
* and continue with the build (or show auth prompt).
|
|
239
|
+
*/
|
|
240
|
+
describe("Process hook (with --wait-plugins)", () => {
|
|
241
|
+
it.skipIf(!HAS_WAIT_PLUGINS)(
|
|
242
|
+
"builds with matters plugin - handles missing auth gracefully",
|
|
243
|
+
async () => {
|
|
244
|
+
const fixture = createFixture({
|
|
245
|
+
withPlugin: true,
|
|
246
|
+
content: {
|
|
247
|
+
"index.md": "# My Blog\n\nWelcome!",
|
|
248
|
+
"posts/hello.md": `---
|
|
249
|
+
title: Hello World
|
|
250
|
+
date: 2024-01-01
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
Hello from my blog!
|
|
254
|
+
`,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Build with plugins - process hook will run
|
|
259
|
+
const { stdout, stderr, code } = await runMoss(
|
|
260
|
+
["build", fixture, "--wait-plugins"],
|
|
261
|
+
{ timeout: 60000 }
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const output = stdout + stderr;
|
|
265
|
+
|
|
266
|
+
// Compilation should succeed (plugin handles auth gracefully)
|
|
267
|
+
// The site should be generated regardless of Matters sync status
|
|
268
|
+
expect(code).toBe(0);
|
|
269
|
+
|
|
270
|
+
// Site should be created
|
|
271
|
+
const siteDir = path.join(fixture, ".moss", "site");
|
|
272
|
+
expect(fs.existsSync(siteDir)).toBe(true);
|
|
273
|
+
|
|
274
|
+
// Output should mention Matters or process hook activity
|
|
275
|
+
// Could be auth prompt, sync status, or graceful skip
|
|
276
|
+
// The exact message depends on plugin behavior
|
|
277
|
+
expect(output).toMatch(/matters|process|sync|compil/i);
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
it.skipIf(!HAS_WAIT_PLUGINS)(
|
|
282
|
+
"shows waiting messages for process hook",
|
|
283
|
+
async () => {
|
|
284
|
+
const fixture = createFixture({
|
|
285
|
+
withPlugin: true,
|
|
286
|
+
content: {
|
|
287
|
+
"index.md": "# Test\n\nContent",
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const { stdout, stderr, code } = await runMoss(
|
|
292
|
+
["build", fixture, "--wait-plugins"],
|
|
293
|
+
{ timeout: 60000 }
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const output = stdout + stderr;
|
|
297
|
+
|
|
298
|
+
// Should show hook waiting messages (from --wait-plugins)
|
|
299
|
+
expect(output).toMatch(/waiting|hook|before_build|process/i);
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Build Integration Tests
|
|
306
|
+
*
|
|
307
|
+
* Tests that the plugin doesn't break normal build flow.
|
|
308
|
+
*/
|
|
309
|
+
describe("Build integration", () => {
|
|
310
|
+
it.skipIf(!HAS_WAIT_PLUGINS)(
|
|
311
|
+
"build completes even when matters sync fails",
|
|
312
|
+
async () => {
|
|
313
|
+
const fixture = createFixture({
|
|
314
|
+
withPlugin: true,
|
|
315
|
+
withMattersConfig: {
|
|
316
|
+
// No username configured - sync will fail gracefully
|
|
317
|
+
sync_on_build: true,
|
|
318
|
+
},
|
|
319
|
+
content: {
|
|
320
|
+
"index.md": "# Blog\n\nWelcome to my blog.",
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const { stdout, stderr, code } = await runMoss(
|
|
325
|
+
["build", fixture, "--wait-plugins"],
|
|
326
|
+
{ timeout: 60000 }
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Build should still complete successfully
|
|
330
|
+
expect(code).toBe(0);
|
|
331
|
+
|
|
332
|
+
// Site should be generated
|
|
333
|
+
const siteDir = path.join(fixture, ".moss", "site");
|
|
334
|
+
expect(fs.existsSync(siteDir)).toBe(true);
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Feature: Fetch Articles from Matters API
|
|
2
|
+
As a plugin
|
|
3
|
+
I want to fetch articles via GraphQL
|
|
4
|
+
So that I can sync content locally
|
|
5
|
+
|
|
6
|
+
# Note: Uses matters.icu test environment with user "yhh354" (has articles)
|
|
7
|
+
# Can override with MATTERS_TEST_USER env var
|
|
8
|
+
|
|
9
|
+
@e2e @real-api
|
|
10
|
+
Scenario: Fetch public user articles
|
|
11
|
+
Given the matters.icu test environment
|
|
12
|
+
When I query articles for user "yhh354"
|
|
13
|
+
Then I should receive a list of articles
|
|
14
|
+
And each article should have id, title, shortHash, and content
|
|
15
|
+
|
|
16
|
+
@e2e @real-api
|
|
17
|
+
Scenario: Handle pagination for users with many articles
|
|
18
|
+
Given the matters.icu test environment
|
|
19
|
+
When I fetch all articles for user "yhh354" with pagination
|
|
20
|
+
Then I should receive all articles across multiple pages
|
|
21
|
+
And all articles should have unique shortHashes
|
|
22
|
+
|
|
23
|
+
@e2e @real-api
|
|
24
|
+
Scenario: Fetch user profile
|
|
25
|
+
Given the matters.icu test environment
|
|
26
|
+
When I query profile for user "yhh354"
|
|
27
|
+
Then I should receive profile with userName and displayName
|
|
28
|
+
|
|
29
|
+
@e2e @real-api
|
|
30
|
+
Scenario: Fetch user collections
|
|
31
|
+
Given the matters.icu test environment
|
|
32
|
+
When I query collections for user "yhh354"
|
|
33
|
+
Then I should receive a list of collections or empty list
|
|
34
|
+
|
|
35
|
+
@e2e @real-api
|
|
36
|
+
Scenario: Handle non-existent user gracefully
|
|
37
|
+
Given the matters.icu test environment
|
|
38
|
+
When I query articles for user "nonexistent_user_xyz_12345"
|
|
39
|
+
Then the query should return null user
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
@e2e @real-api
|
|
2
|
+
Feature: Wallet Authentication
|
|
3
|
+
As a developer, I want to authenticate with Ethereum wallet
|
|
4
|
+
So that I can run e2e tests without email verification
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given I am using the Matters test environment
|
|
8
|
+
|
|
9
|
+
Scenario: Login with valid wallet signature
|
|
10
|
+
Given I have a valid Ethereum private key
|
|
11
|
+
When I complete the wallet login flow
|
|
12
|
+
Then I should receive an auth token
|
|
13
|
+
And I should receive my user info
|
|
14
|
+
And the type should be "Login" or "Signup"
|
|
15
|
+
|
|
16
|
+
Scenario: Generate signing message
|
|
17
|
+
Given I have a valid Ethereum address
|
|
18
|
+
When I request a signing message for login
|
|
19
|
+
Then I should receive a nonce
|
|
20
|
+
And I should receive a signingMessage
|
|
21
|
+
And the message should contain the address
|
|
22
|
+
|
|
23
|
+
Scenario: Create authenticated client
|
|
24
|
+
Given I have completed wallet login
|
|
25
|
+
And I have an auth token
|
|
26
|
+
When I create an authenticated client
|
|
27
|
+
Then the client should be able to make authenticated requests
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Feature: Download Retry Logic
|
|
2
|
+
As a developer
|
|
3
|
+
I want downloads to retry on transient failures
|
|
4
|
+
So that temporary issues don't cause permanent failures
|
|
5
|
+
|
|
6
|
+
Scenario: Retries with Fibonacci backoff on 503
|
|
7
|
+
Given a mock Tauri environment
|
|
8
|
+
Given an in-memory filesystem
|
|
9
|
+
Given an image URL that returns 503 twice then succeeds
|
|
10
|
+
When I download the image with retry enabled
|
|
11
|
+
Then it should retry with Fibonacci delays
|
|
12
|
+
And the download should succeed on attempt 3
|
|
13
|
+
|
|
14
|
+
Scenario: Gives up after max retries
|
|
15
|
+
Given a mock Tauri environment
|
|
16
|
+
Given an in-memory filesystem
|
|
17
|
+
Given an image URL that always returns 503
|
|
18
|
+
When I download the image with max 3 retries
|
|
19
|
+
Then it should attempt 4 times total
|
|
20
|
+
And the download should fail with 503 error
|
|
21
|
+
|
|
22
|
+
Scenario: Does not retry on 404
|
|
23
|
+
Given a mock Tauri environment
|
|
24
|
+
Given an in-memory filesystem
|
|
25
|
+
Given an image URL that returns 404
|
|
26
|
+
When I download the image with retry enabled
|
|
27
|
+
Then it should not retry
|
|
28
|
+
And the download should fail immediately with 404 error
|
|
29
|
+
|
|
30
|
+
Scenario: Retries on network timeout
|
|
31
|
+
Given a mock Tauri environment
|
|
32
|
+
Given an in-memory filesystem
|
|
33
|
+
Given an image URL that times out twice then succeeds
|
|
34
|
+
When I download the image with retry enabled
|
|
35
|
+
Then it should retry after timeouts
|
|
36
|
+
And the download should succeed on attempt 3
|