dsh-sidebar-row-menu 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-sidebar-row-menu
2
2
 
3
- <img src="assets/icon-180.png" width="96" align="right" alt="Plugin icon" />
3
+ <img src="https://raw.githubusercontent.com/TLEWA/dsh-sidebar-row-menu/main/assets/icon-180.png" width="96" align="right" alt="Plugin icon" />
4
4
 
5
5
  [![CI](https://github.com/TLEWA/dsh-sidebar-row-menu/actions/workflows/ci.yml/badge.svg)](https://github.com/TLEWA/dsh-sidebar-row-menu/actions/workflows/ci.yml)
6
6
  [![Release](https://github.com/TLEWA/dsh-sidebar-row-menu/actions/workflows/release.yml/badge.svg)](https://github.com/TLEWA/dsh-sidebar-row-menu/releases)
@@ -73,7 +73,33 @@ Requirements:
73
73
  - Windows PowerShell 5.1+;
74
74
  - Row components from the `0.2.1-rc.5` generation (see [Compatibility](#compatibility)).
75
75
 
76
- ### Install from GitHub (recommended)
76
+ ### Install from npm (recommended)
77
+
78
+ The package is published to the npm registry, and DSH's plugin installer can
79
+ resolve it directly by name:
80
+
81
+ 1. Open **Plugins** in the sidebar → **Install plugin**;
82
+ 2. Enter the package name and confirm:
83
+
84
+ ```
85
+ dsh-sidebar-row-menu
86
+ ```
87
+
88
+ 3. Hard-refresh (`Ctrl+Shift+R`); restart the desktop app if the host half is not active.
89
+
90
+ Pin a version by appending it to the name:
91
+
92
+ ```
93
+ dsh-sidebar-row-menu@1.3.3
94
+ ```
95
+
96
+ Equivalent command line:
97
+
98
+ ```powershell
99
+ dsh plugin --profile desktop add dsh-sidebar-row-menu@1.3.3
100
+ ```
101
+
102
+ ### Install from GitHub
77
103
 
78
104
  DSH Desktop installs Git dependencies through pnpm. The `lib/` directory in this
79
105
  repository is already runnable, so **no local build is required**.
@@ -90,13 +116,13 @@ repository is already runnable, so **no local build is required**.
90
116
  That URL tracks the latest commit on `main`. Pin a tag for a stable version:
91
117
 
92
118
  ```
93
- https://github.com/TLEWA/dsh-sidebar-row-menu#v1.3.2
119
+ https://github.com/TLEWA/dsh-sidebar-row-menu#v1.3.3
94
120
  ```
95
121
 
96
122
  Or install the tarball built by Actions directly, without Git:
97
123
 
98
124
  ```
99
- https://github.com/TLEWA/dsh-sidebar-row-menu/releases/download/v1.3.2/dsh-sidebar-row-menu-1.3.2.tgz
125
+ https://github.com/TLEWA/dsh-sidebar-row-menu/releases/download/v1.3.3/dsh-sidebar-row-menu-1.3.3.tgz
100
126
  ```
101
127
 
102
128
  Equivalent command line:
@@ -175,10 +201,10 @@ repository itself is untouched.
175
201
  > as relative to the profile, and cross-drive paths produce broken links. The
176
202
  > script exists to avoid exactly that.
177
203
 
178
- ### Switching from the script install to the GitHub install
204
+ ### Switching from the script install to a pnpm install (npm / GitHub)
179
205
 
180
- If you installed the development copy with `scripts/install.ps1` and then paste
181
- the GitHub URL into the Plugins page, pnpm may fail with:
206
+ If you installed the development copy with `scripts/install.ps1` and then enter
207
+ the npm package name or the GitHub URL into the Plugins page, pnpm may fail with:
182
208
 
183
209
  ```
184
210
  [ERR_PNPM_EPERM] [importPackage ...\node_modules\dsh-sidebar-row-menu]
@@ -203,7 +229,7 @@ cmd /c rmdir "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-sidebar-ro
203
229
  ```
204
230
 
205
231
  A fresh profile never hits this; it only affects the "script install, then
206
- GitHub install" transition.
232
+ pnpm install (npm or GitHub)" transition.
207
233
 
208
234
  ## Development
209
235
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-sidebar-row-menu
2
2
 
3
- <img src="assets/icon-180.png" width="96" align="right" alt="插件图标" />
3
+ <img src="https://raw.githubusercontent.com/TLEWA/dsh-sidebar-row-menu/main/assets/icon-180.png" width="96" align="right" alt="插件图标" />
4
4
 
5
5
  [![CI](https://github.com/TLEWA/dsh-sidebar-row-menu/actions/workflows/ci.yml/badge.svg)](https://github.com/TLEWA/dsh-sidebar-row-menu/actions/workflows/ci.yml)
6
6
  [![Release](https://github.com/TLEWA/dsh-sidebar-row-menu/actions/workflows/release.yml/badge.svg)](https://github.com/TLEWA/dsh-sidebar-row-menu/releases)
@@ -66,7 +66,32 @@
66
66
  - Windows PowerShell 5.1+;
67
67
  - 与 DSH 客户端内置包 `0.2.1-rc.5` 同代的行组件(见[兼容性](#兼容性))。
68
68
 
69
- ### 从 GitHub 直接安装(推荐)
69
+ ### 从 npm 安装(推荐)
70
+
71
+ 包已发布到 npm registry,DSH 的插件安装器可以直接按包名解析:
72
+
73
+ 1. 打开侧边栏 **插件** 页 → **安装插件**;
74
+ 2. 输入包名并确认:
75
+
76
+ ```
77
+ dsh-sidebar-row-menu
78
+ ```
79
+
80
+ 3. 安装完成后硬刷新(`Ctrl+Shift+R`);宿主半边未生效时重启桌面端。
81
+
82
+ 要固定版本,在包名后加版本号:
83
+
84
+ ```
85
+ dsh-sidebar-row-menu@1.3.3
86
+ ```
87
+
88
+ 命令行等价写法:
89
+
90
+ ```powershell
91
+ dsh plugin --profile desktop add dsh-sidebar-row-menu@1.3.3
92
+ ```
93
+
94
+ ### 从 GitHub 安装
70
95
 
71
96
  DSH 桌面端通过 pnpm 安装 Git 依赖;本仓库的 `lib/` 已是可直接运行的产物,安装时
72
97
  **不需要本地构建**。
@@ -83,13 +108,13 @@ DSH 桌面端通过 pnpm 安装 Git 依赖;本仓库的 `lib/` 已是可直接
83
108
  该地址跟随 `main` 的最新提交。要固定版本,在地址后加标签:
84
109
 
85
110
  ```
86
- https://github.com/TLEWA/dsh-sidebar-row-menu#v1.3.2
111
+ https://github.com/TLEWA/dsh-sidebar-row-menu#v1.3.3
87
112
  ```
88
113
 
89
114
  也可以直接安装 Actions 打出的压缩包(不经过 Git):
90
115
 
91
116
  ```
92
- https://github.com/TLEWA/dsh-sidebar-row-menu/releases/download/v1.3.2/dsh-sidebar-row-menu-1.3.2.tgz
117
+ https://github.com/TLEWA/dsh-sidebar-row-menu/releases/download/v1.3.3/dsh-sidebar-row-menu-1.3.3.tgz
93
118
  ```
94
119
 
95
120
  命令行等价写法:
@@ -158,9 +183,9 @@ powershell -NoProfile -File .\scripts\install.ps1 -Uninstall
158
183
  > 不要用 `dsh plugin add link:<绝对路径>`:pnpm 在 Windows 上把 `link:` 当成相对
159
184
  > profile 的路径,跨盘时会生成坏链接;脚本正是为了绕开这一点。
160
185
 
161
- ### 从脚本安装切换到 GitHub 安装
186
+ ### 从脚本安装切换到 pnpm 安装(npm / GitHub)
162
187
 
163
- 如果你先用 `scripts/install.ps1` 装过开发版,再在插件页输入 GitHub 地址,pnpm 可能报:
188
+ 如果你先用 `scripts/install.ps1` 装过开发版,再在插件页输入 npm 包名或 GitHub 地址,pnpm 可能报:
164
189
 
165
190
  ```
166
191
  [ERR_PNPM_EPERM] [importPackage ...\node_modules\dsh-sidebar-row-menu]
@@ -181,7 +206,7 @@ powershell -NoProfile -File .\scripts\install.ps1 -Uninstall
181
206
  cmd /c rmdir "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-sidebar-row-menu"
182
207
  ```
183
208
 
184
- 全新 profile 不会遇到这个问题;它只影响“先脚本安装、后 GitHub 安装”的切换场景。
209
+ 全新 profile 不会遇到这个问题;它只影响“先脚本安装、后 pnpm(npm 或 GitHub)安装”的切换场景。
185
210
 
186
211
  ## 开发
187
212
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sidebar-row-menu",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "type": "module",
5
5
  "description": "DSH sidebar right-click menus: a self-composed menu for workspace rows, and the native slot menu opened at the pointer for session rows.",
6
6
  "keywords": [
@@ -31,6 +31,7 @@
31
31
  "lib",
32
32
  "assets",
33
33
  "scripts/install.ps1",
34
+ "scripts/marketplace-preflight.mjs",
34
35
  "cordis.patch.yml",
35
36
  "README.md",
36
37
  "README.en.md",
@@ -0,0 +1,169 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..');
6
+ const offline = process.argv.includes('--offline');
7
+ const entryPath = join(root, 'marketplace', 'TLEWA__dsh-sidebar-row-menu.yml');
8
+ const iconPath = join(root, 'assets', 'icon-180.png');
9
+ const repoSlug = 'TLEWA/dsh-sidebar-row-menu';
10
+
11
+ let failures = 0;
12
+ let warnings = 0;
13
+
14
+ const pass = (message, detail = '') => console.log(`PASS ${message}${detail ? ` — ${detail}` : ''}`);
15
+ const warn = (message, detail = '') => {
16
+ warnings += 1;
17
+ console.log(`WARN ${message}${detail ? ` — ${detail}` : ''}`);
18
+ };
19
+ const fail = (message, detail = '') => {
20
+ failures += 1;
21
+ console.log(`FAIL ${message}${detail ? ` — ${detail}` : ''}`);
22
+ };
23
+
24
+ // The submission entry is local-only (git-ignored), so a fresh clone has no
25
+ // marketplace/ directory. Treat that as "nothing to check", not as a failure.
26
+ if (!existsSync(entryPath)) {
27
+ warn('marketplace entry', 'marketplace/ is local-only and absent in this checkout');
28
+ console.log('\nREADY — 0 failure(s), 1 warning(s)');
29
+ process.exit(0);
30
+ }
31
+
32
+ function readPngSize(file) {
33
+ const buffer = readFileSync(file);
34
+ const signature = buffer.subarray(0, 8).toString('hex');
35
+ if (signature !== '89504e470d0a1a0a') return null;
36
+ return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
37
+ }
38
+
39
+ // --- package manifest -------------------------------------------------------
40
+
41
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
42
+ pkg.name === 'dsh-sidebar-row-menu'
43
+ ? pass('package name', pkg.name)
44
+ : fail('package name', `expected dsh-sidebar-row-menu, got ${pkg.name}`);
45
+
46
+ if (pkg.dsh?.bundle?.patch) {
47
+ pass('dsh.bundle.patch', pkg.dsh.bundle.patch);
48
+ const patchPath = join(root, pkg.dsh.bundle.patch);
49
+ if (!existsSync(patchPath)) {
50
+ fail('bundle patch exists', pkg.dsh.bundle.patch);
51
+ } else {
52
+ const patch = readFileSync(patchPath, 'utf8');
53
+ if (/insert:/.test(patch) && patch.includes(pkg.name)) {
54
+ pass('bundle patch content', 'insert + package name');
55
+ } else {
56
+ fail('bundle patch content', 'expected an insert entry naming the package');
57
+ }
58
+ }
59
+ } else {
60
+ fail('dsh.bundle.patch', 'missing — dsh.client alone is not installable');
61
+ }
62
+
63
+ pkg.license === 'MIT' ? pass('license', 'MIT') : fail('license', `got ${pkg.license}`);
64
+ String(pkg.repository?.url ?? '').includes(repoSlug)
65
+ ? pass('repository', pkg.repository.url)
66
+ : fail('repository', 'does not point at the GitHub repo');
67
+
68
+ // --- storefront icon --------------------------------------------------------
69
+
70
+ if (!existsSync(iconPath)) {
71
+ fail('assets/icon-180.png', 'missing');
72
+ } else {
73
+ const size = readPngSize(iconPath);
74
+ if (!size) {
75
+ fail('icon format', 'not a PNG');
76
+ } else if (size.width !== 180 || size.height !== 180) {
77
+ fail('icon dimensions', `${size.width}x${size.height}, expected 180x180`);
78
+ } else {
79
+ pass('icon', '180x180 PNG');
80
+ }
81
+ }
82
+
83
+ // --- submission entry -------------------------------------------------------
84
+
85
+ if (!existsSync(entryPath)) {
86
+ fail('marketplace entry', 'missing');
87
+ } else {
88
+ const entry = readFileSync(entryPath, 'utf8');
89
+ const field = (name) => (entry.match(new RegExp(`^${name}:\\s*(.+)$`, 'm')) ?? [])[1]?.trim() ?? '';
90
+ const checks = [
91
+ ['url', `https://github.com/${repoSlug}`],
92
+ ['name', repoSlug],
93
+ ['category', 'ui'],
94
+ ['icon', `https://raw.githubusercontent.com/${repoSlug}/main/assets/icon-180.png`],
95
+ ];
96
+ for (const [key, expected] of checks) {
97
+ const actual = field(key);
98
+ actual === expected
99
+ ? pass(`entry.${key}`, actual)
100
+ : fail(`entry.${key}`, `expected ${expected}, got ${actual || '(missing)'}`);
101
+ }
102
+ /^\s+en:\s*\S/m.test(entry) ? pass('entry.description.en') : fail('entry.description.en', 'missing');
103
+ /^\s+zh:\s*\S/m.test(entry) ? pass('entry.description.zh') : warn('entry.description.zh', 'missing (optional)');
104
+ if (!/^tarball:\s*https:\/\/github\.com\/.+\/releases\/(download|latest\/download)\/.+\.tgz$/m.test(entry)) {
105
+ fail('entry.tarball', 'missing or not a GitHub release .tgz URL');
106
+ } else {
107
+ pass('entry.tarball');
108
+ }
109
+ }
110
+
111
+ // --- repository live checks -------------------------------------------------
112
+
113
+ if (offline) {
114
+ warn('GitHub checks', 'skipped (--offline)');
115
+ } else {
116
+ try {
117
+ const response = await fetch(`https://api.github.com/repos/${repoSlug}`, {
118
+ headers: { accept: 'application/vnd.github+json', 'user-agent': 'dsh-sidebar-row-menu-preflight' },
119
+ signal: AbortSignal.timeout(15000),
120
+ });
121
+ if (!response.ok) throw new Error(`GitHub API ${response.status}`);
122
+ const repo = await response.json();
123
+
124
+ const created = Date.parse(repo.created_at);
125
+ const ageMs = Date.now() - created;
126
+ const eligible = new Date(created + 86400000);
127
+ if (ageMs >= 86400000) {
128
+ pass('repo age', `${(ageMs / 86400000).toFixed(1)} days`);
129
+ } else {
130
+ warn(
131
+ 'repo age',
132
+ `${(ageMs / 3600000).toFixed(1)} hours — eligible at ${eligible.toISOString()} (${eligible.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })})`,
133
+ );
134
+ }
135
+
136
+ Array.isArray(repo.topics) && repo.topics.includes('dsh-plugin')
137
+ ? pass('topic', 'dsh-plugin')
138
+ : fail('topic', 'dsh-plugin is missing — required by awesome-dsh-plugin');
139
+
140
+ repo.description ? pass('repo description', repo.description) : fail('repo description', 'empty');
141
+
142
+ if (existsSync(entryPath)) {
143
+ const entry = readFileSync(entryPath, 'utf8');
144
+ const tarball = (entry.match(/^tarball:\s*(\S+)$/m) ?? [])[1];
145
+ if (tarball) {
146
+ const head = await fetch(tarball, {
147
+ method: 'HEAD',
148
+ redirect: 'follow',
149
+ signal: AbortSignal.timeout(20000),
150
+ });
151
+ head.ok
152
+ ? pass('tarball URL', `${head.status} ${tarball}`)
153
+ : fail('tarball URL', `${head.status} ${tarball}`);
154
+ }
155
+ }
156
+
157
+ const iconUrl = `https://raw.githubusercontent.com/${repoSlug}/main/assets/icon-180.png`;
158
+ const iconHead = await fetch(iconUrl, { method: 'HEAD', signal: AbortSignal.timeout(15000) });
159
+ iconHead.ok ? pass('icon URL', `${iconHead.status}`) : fail('icon URL', `${iconHead.status}`);
160
+ } catch (error) {
161
+ fail('GitHub checks', error instanceof Error ? error.message : String(error));
162
+ }
163
+ }
164
+
165
+ console.log(`\n${failures === 0 ? 'READY' : 'NOT READY'} — ${failures} failure(s), ${warnings} warning(s)`);
166
+ if (failures > 0) {
167
+ console.log('Fix the FAIL lines before opening the awesome-dsh-plugin pull request.');
168
+ process.exitCode = 1;
169
+ }