dsh-featherhunter-plugin-base 0.0.0-stage → 0.0.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.
@@ -0,0 +1,199 @@
1
+ /**
2
+ * README block extraction and normalized comparison.
3
+ *
4
+ * The five plugin repositories hand-write four README blocks (contact, star
5
+ * history, thanks, author plugins) in slightly different shapes. This module
6
+ * (a) locates those blocks in a README text, (b) parses each shape leniently
7
+ * into a comparable structure, and (c) compares two blocks by *content*
8
+ * (members / links / texts / widths / thanks table data), reporting pure
9
+ * formatting differences separately.
10
+ *
11
+ * It is deliberately dependency-free (no node: imports) so both the ./node
12
+ * entry and the fixture extraction script can use it.
13
+ */
14
+ /** Kind of a managed README block. */
15
+ export type ReadmeBlockKind = "contact" | "star-history" | "thanks" | "more";
16
+ /** A line that looks like merge-conflict residue; writers must stop. */
17
+ export interface SuspiciousLine {
18
+ /** 1-based line number. */
19
+ line: number;
20
+ /** The offending line, verbatim. */
21
+ text: string;
22
+ reason: "conflict-start" | "conflict-separator" | "conflict-end" | "merge-conflict-residue";
23
+ }
24
+ /** Raw block slices located in a README text. */
25
+ export interface LocatedBlocks {
26
+ /** Raw block text per kind; null when the block is absent. */
27
+ blocks: Record<ReadmeBlockKind, string | null>;
28
+ /** Block kinds in the order they appear in the file. */
29
+ order: ReadmeBlockKind[];
30
+ /** Merge-conflict residue / suspicious lines found anywhere in the file. */
31
+ suspicious: SuspiciousLine[];
32
+ }
33
+ /**
34
+ * Locate the four managed blocks in a README text.
35
+ *
36
+ * Boundaries are the next managed-block start (or EOF), trimmed of blank lines
37
+ * and trailing `---` rules. Block order is recorded but never assumed: the
38
+ * five repos currently order the blocks differently.
39
+ */
40
+ export declare function locateReadmeBlocks(markdown: string): LocatedBlocks;
41
+ /** Find merge-conflict residue. Writers must stop and hand the file to a human. */
42
+ export declare function findSuspiciousLines(markdown: string): SuspiciousLine[];
43
+ /** Parse an HTML attribute string into a lower-cased attribute map. */
44
+ export declare function parseAttributes(attributeText: string): Record<string, string>;
45
+ /** File name of an image src, dropping directory prefix, query and hash. */
46
+ export declare function assetName(src: string): string;
47
+ /** Parsed block heading; `sub` is the small label (CONNECT / THANKS / MORE). */
48
+ export interface ParsedHeading {
49
+ sub: string | null;
50
+ text: string;
51
+ }
52
+ /** Parse the heading of a managed block in either HTML or Markdown form. */
53
+ export declare function parseHeading(raw: string): ParsedHeading | null;
54
+ /** One group (chat group QR code) inside the contact block. */
55
+ export interface ContactGroup {
56
+ /** Table header text, e.g. 飞书群(主). */
57
+ title: string;
58
+ /** Image src exactly as written (relative or absolute). */
59
+ src: string;
60
+ /** Image file name; stable across relative prefixes and CDN URLs. */
61
+ asset: string;
62
+ /** Rendered image width, if any. */
63
+ width: number | null;
64
+ alt: string;
65
+ }
66
+ /** Parsed contact block. */
67
+ export interface ParsedContactBlock {
68
+ variant: "html-heading" | "markdown-heading";
69
+ heading: ParsedHeading | null;
70
+ welcome: string | null;
71
+ groups: ContactGroup[];
72
+ }
73
+ /** Parse the contact block in either heading variant. */
74
+ export declare function parseContactBlock(raw: string): ParsedContactBlock;
75
+ /** A dark/light `<source>` inside a `<picture>`. */
76
+ export interface StarSource {
77
+ /** "dark" / "light" when declared, otherwise null. */
78
+ media: string | null;
79
+ src: string;
80
+ /** Image file name, stable across CDN URLs and cache busters. */
81
+ asset: string;
82
+ }
83
+ /** The visible star history image. */
84
+ export interface StarImage {
85
+ alt: string;
86
+ src: string;
87
+ asset: string;
88
+ width: number | null;
89
+ }
90
+ /** Parsed star history block. */
91
+ export interface ParsedStarHistoryBlock {
92
+ variant: "picture-link" | "picture" | "markdown-image" | "unknown";
93
+ /** Click-through link around the picture, e.g. deck's star-history.html. */
94
+ link: string | null;
95
+ sources: StarSource[];
96
+ image: StarImage | null;
97
+ /** Caption below the image (im keeps a two-axis explanation). */
98
+ caption: string | null;
99
+ }
100
+ /** Parse the star history block in any of its four current shapes. */
101
+ export declare function parseStarHistoryBlock(raw: string): ParsedStarHistoryBlock;
102
+ /** One table cell of the thanks table. */
103
+ export interface ThanksCell {
104
+ /** Visible text (links reduced to their label). */
105
+ text: string;
106
+ /** Link targets found in the cell, in order. */
107
+ links: string[];
108
+ }
109
+ /** One row of the thanks table. */
110
+ export interface ThanksRow {
111
+ cells: ThanksCell[];
112
+ }
113
+ /** Parsed thanks block. */
114
+ export interface ParsedThanksBlock {
115
+ variant: "html-heading" | "markdown-heading";
116
+ heading: ParsedHeading | null;
117
+ /** Language of the table markers (THANKS-TABLE-ZH / -EN). */
118
+ lang: "zh" | "en" | null;
119
+ headers: string[];
120
+ rows: ThanksRow[];
121
+ /** Non-table prose around the table, kept for fixtures but not compared. */
122
+ prose: string | null;
123
+ }
124
+ /** Parse the thanks block, including the empty-table case used by ilife. */
125
+ export declare function parseThanksBlock(raw: string): ParsedThanksBlock;
126
+ /** One plugin row in the "more from the author" block. */
127
+ export interface MoreItem {
128
+ name: string;
129
+ url: string;
130
+ description: string;
131
+ }
132
+ /** Parsed "more from the author" block. */
133
+ export interface ParsedMoreBlock {
134
+ variant: "html-heading" | "markdown-heading";
135
+ heading: ParsedHeading | null;
136
+ intro: string | null;
137
+ items: MoreItem[];
138
+ footer: string | null;
139
+ }
140
+ /** Parse the "more from the author" block. Rows are returned in file order. */
141
+ export declare function parseMoreBlock(raw: string): ParsedMoreBlock;
142
+ /** Formatting-only differences; they never make a block content-different. */
143
+ export type FormatStage = "line-endings" | "whitespace" | "quotes" | "attribute-order" | "self-closing";
144
+ /** A formatting difference kind (`markup` = structural markup beyond the stages). */
145
+ export type FormatDifferenceKind = FormatStage | "heading-variant" | "markup";
146
+ /** One detected pure-format difference. */
147
+ export interface FormatDifference {
148
+ kind: FormatDifferenceKind;
149
+ /** Set when the difference was found at README level. */
150
+ block?: ReadmeBlockKind;
151
+ }
152
+ /** Content difference dimensions required by the equivalence criterion. */
153
+ export type DifferenceDimension = "presence" | "members" | "links" | "text" | "width" | "table" | "image";
154
+ /** One content difference, addressed by a structural path. */
155
+ export interface Difference {
156
+ dimension: DifferenceDimension;
157
+ path: string;
158
+ expected: unknown;
159
+ actual: unknown;
160
+ }
161
+ /** Comparison options. */
162
+ export interface CompareOptions {
163
+ /**
164
+ * How image sources are compared. `basename` (default) ignores relative/CDN
165
+ * prefixes and `?v=` cache busters; `exact` compares the raw src.
166
+ */
167
+ imageSrc?: "basename" | "exact";
168
+ }
169
+ /** Result of comparing one managed block. */
170
+ export interface BlockComparison {
171
+ kind: ReadmeBlockKind;
172
+ /** Raw text is byte-identical. */
173
+ identical: boolean;
174
+ /** Content is equivalent under the F18 criterion. */
175
+ equivalent: boolean;
176
+ contentDifferences: Difference[];
177
+ /** Only populated when content is equivalent but raw text differs. */
178
+ formatDifferences: FormatDifference[];
179
+ }
180
+ /** Result of comparing two READMEs. */
181
+ export interface ReadmeComparison {
182
+ equivalent: boolean;
183
+ blocks: Record<ReadmeBlockKind, BlockComparison>;
184
+ contentDifferences: Difference[];
185
+ formatDifferences: FormatDifference[];
186
+ order: {
187
+ expected: ReadmeBlockKind[];
188
+ actual: ReadmeBlockKind[];
189
+ same: boolean;
190
+ };
191
+ /** Merge-conflict residue from the actual README: callers must stop. */
192
+ suspicious: SuspiciousLine[];
193
+ }
194
+ /** Canonical form that erases whitespace, quote, attribute-order and self-close style. */
195
+ export declare function canonicalizeMarkup(markup: string): string;
196
+ /** Compare one managed block; null means the block is absent. */
197
+ export declare function compareBlocks(kind: ReadmeBlockKind, expectedRaw: string | null, actualRaw: string | null, options?: CompareOptions): BlockComparison;
198
+ /** Compare two READMEs (raw text or already-located blocks) block by block. */
199
+ export declare function compareReadmeBlocks(expected: string | LocatedBlocks, actual: string | LocatedBlocks, options?: CompareOptions): ReadmeComparison;
@@ -0,0 +1,37 @@
1
+ import type { Lang, Manifest } from "../core/types";
2
+ /** Shared options for the README block renderers. */
3
+ export interface BlockRenderOptions {
4
+ /** Manifest to render from; defaults to the packaged baseline manifest. */
5
+ manifest?: Manifest;
6
+ /** Published package version, used for precise CDN URLs. */
7
+ version?: string;
8
+ /** Target language of the generated block. */
9
+ lang?: Lang;
10
+ /** CDN base URL override (tests / mirrors). */
11
+ cdnBase?: string;
12
+ }
13
+ /** Marker names of the four managed README blocks. */
14
+ export type BlockName = "contact" | "star-history" | "thanks" | "more";
15
+ export interface VerifyResult {
16
+ ok: boolean;
17
+ errors: string[];
18
+ }
19
+ /** Options for the README writer (ticket 08). */
20
+ export interface ReadmeSyncOptions {
21
+ /** Repository roots to update. */
22
+ roots: string[];
23
+ /** Report drift without writing. */
24
+ check?: boolean;
25
+ /** Rewrite every managed block, even unchanged ones. */
26
+ all?: boolean;
27
+ dryRun?: boolean;
28
+ }
29
+ export interface DoctorCheck {
30
+ name: string;
31
+ ok: boolean;
32
+ detail: string;
33
+ }
34
+ export interface DoctorResult {
35
+ ok: boolean;
36
+ checks: DoctorCheck[];
37
+ }
package/package.json CHANGED
@@ -1,6 +1,74 @@
1
1
  {
2
2
  "name": "dsh-featherhunter-plugin-base",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "FeatherHunter DSH plugin shared base: settings-panel contact modal / author plugins card widgets and README block renderers",
5
+ "keywords": [
6
+ "dsh",
7
+ "deepseek-harness",
8
+ "plugin",
9
+ "readme",
10
+ "contact"
11
+ ],
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "data",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "exports": {
25
+ "./core": {
26
+ "types": "./dist/core/index.d.ts",
27
+ "import": "./dist/core/index.js",
28
+ "default": "./dist/core/index.js"
29
+ },
30
+ "./client": {
31
+ "types": "./dist/client/index.d.ts",
32
+ "import": "./dist/client/index.js",
33
+ "default": "./dist/client/index.js"
34
+ },
35
+ "./node": {
36
+ "types": "./dist/node/index.d.ts",
37
+ "import": "./dist/node/index.js",
38
+ "default": "./dist/node/index.js"
39
+ },
40
+ "./package.json": "./package.json"
41
+ },
42
+ "bin": {
43
+ "dsh-base": "dist/node/cli.js"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^18 || ^19"
47
+ },
48
+ "scripts": {
49
+ "build": "node build.mjs",
50
+ "build:demo": "node build.mjs --demo",
51
+ "build:data": "node scripts/run-ts.mjs build-data",
52
+ "data:check": "node scripts/run-ts.mjs build-data --check",
53
+ "contact:update": "node scripts/run-ts.mjs contact-update",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "test:network": "node scripts/cdn-smoke.mjs",
58
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^26.6.6",
62
+ "@types/pngjs": "^6.0.5",
63
+ "@types/react": "^19.3.0",
64
+ "@types/react-dom": "^19.3.0",
65
+ "esbuild": "^0.28.2",
66
+ "jpeg-js": "^0.4.4",
67
+ "jsdom": "^30.1.3",
68
+ "pngjs": "^7.0.0",
69
+ "react": "^19.3.1",
70
+ "react-dom": "^19.3.1",
71
+ "typescript": "^7.0.2",
72
+ "vitest": "^5.0.5"
73
+ }
74
+ }