dsh-plugin-toolkit 0.8.4 → 0.8.6
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 +18 -0
- package/README.md +22 -0
- package/lib/cli.mjs +6 -0
- package/lib/release.mjs +36 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 0.8.6
|
|
2
|
+
|
|
3
|
+
- `release --publish`: run `npm publish` locally instead of waiting for a
|
|
4
|
+
publish workflow. Plenty of small plugins have no release pipeline; before
|
|
5
|
+
this, `release` tagged, pushed and then waited for a registry that would never
|
|
6
|
+
receive the version.
|
|
7
|
+
- The publish failure reports the last line of npm's stderr, so a 403 reads like
|
|
8
|
+
a 403.
|
|
9
|
+
|
|
10
|
+
## 0.8.5
|
|
11
|
+
|
|
12
|
+
- `release --market <dir>`: write the index entry into a checkout of the
|
|
13
|
+
repository that owns the index, and open the pull request there. Before this,
|
|
14
|
+
`release` only touched a `market/index.json` inside the plugin's own repo, so
|
|
15
|
+
anyone listed in a central market had to patch the entry by hand — which is
|
|
16
|
+
where the timestamp goes wrong.
|
|
17
|
+
- `--index` still names the file inside that repository.
|
|
18
|
+
|
|
1
19
|
## 0.8.4
|
|
2
20
|
|
|
3
21
|
- README: `## When the panel does not appear` — the five checks that explain a
|
package/README.md
CHANGED
|
@@ -320,6 +320,28 @@ merged
|
|
|
320
320
|
|
|
321
321
|
Without `--pr` it prints the `git add` / `gh pr create` commands instead of running them; `--dry-run` prints the whole sequence before anything happens. A re-run after a failure is safe: the same version with the same publish time is a no-op.
|
|
322
322
|
|
|
323
|
+
### When nothing publishes for you
|
|
324
|
+
|
|
325
|
+
`release` assumes a tag starts a workflow that publishes. Repositories without
|
|
326
|
+
one use `--publish`, which runs `npm publish` in the same step:
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
$ dsh-plugin release . --publish
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### When the index lives in someone else's repository
|
|
333
|
+
|
|
334
|
+
Most plugins are listed in a central market repository, not their own. Point
|
|
335
|
+
`--market` at a checkout of that repository and the entry — and the pull
|
|
336
|
+
request — land there:
|
|
337
|
+
|
|
338
|
+
```sh
|
|
339
|
+
$ dsh-plugin release . --market ../market-repo --pr --merge
|
|
340
|
+
tagged plugin-v0.4.0, npm served my-plugin@0.4.0 after 92s
|
|
341
|
+
updated /Users/me/market-repo/market/index.json on market/0.4.0
|
|
342
|
+
merged
|
|
343
|
+
```
|
|
344
|
+
|
|
323
345
|
## Exit codes
|
|
324
346
|
|
|
325
347
|
| Code | Meaning |
|
package/lib/cli.mjs
CHANGED
|
@@ -37,6 +37,8 @@ export const HELP = `dsh-plugin — preflight and release DeepSeek Harness plugi
|
|
|
37
37
|
Flags
|
|
38
38
|
--dry-run show the steps without touching anything
|
|
39
39
|
--index <path> index file, default market/index.json
|
|
40
|
+
--market <dir> with release: a checkout of the repository that holds the index
|
|
41
|
+
--publish with release: run npm publish here, for repos without a publish workflow
|
|
40
42
|
--tag-prefix <prefix> default plugin-v
|
|
41
43
|
--budget <seconds> how long to wait for npm, default 480
|
|
42
44
|
--pr commit the index on a branch, push it, open the pull request
|
|
@@ -71,7 +73,9 @@ function flags(argv) {
|
|
|
71
73
|
else if (token === '--timeout') parsed.timeout = Number(argv[++index]);
|
|
72
74
|
else if (token === '--pr') parsed.pr = true;
|
|
73
75
|
else if (token === '--merge') parsed.merge = true;
|
|
76
|
+
else if (token === '--publish') parsed.publish = true;
|
|
74
77
|
else if (token === '--base') parsed.base = argv[++index];
|
|
78
|
+
else if (token === '--market') parsed.market = argv[++index];
|
|
75
79
|
else if (token.startsWith('--') === true) throw new Error(`unknown flag ${token}`);
|
|
76
80
|
else parsed.positional.push(token);
|
|
77
81
|
}
|
|
@@ -210,6 +214,8 @@ export async function run(argv, io = console) {
|
|
|
210
214
|
const result = await release({
|
|
211
215
|
dir,
|
|
212
216
|
indexFile: options.index,
|
|
217
|
+
marketDir: options.market,
|
|
218
|
+
publish: options.publish === true,
|
|
213
219
|
tagPrefix: options.tagPrefix,
|
|
214
220
|
budgetMs: options.budget === undefined ? undefined : options.budget * 1000,
|
|
215
221
|
pr: options.pr === true,
|
package/lib/release.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* registry, then touch the index — the index entry carries the publish time the
|
|
6
6
|
* registry reported, so writing it earlier would mean guessing.
|
|
7
7
|
*/
|
|
8
|
-
import { run } from './exec.mjs';
|
|
8
|
+
import { run as runCommand } from './exec.mjs';
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
@@ -14,11 +14,15 @@ import { addVersion, buildEntry, readIndex, writeIndex } from './index-file.mjs'
|
|
|
14
14
|
import { publishedAt, waitForVersion } from './registry.mjs';
|
|
15
15
|
import { defaultGh, mergePr, openIndexPr, waitForChecks } from './pr.mjs';
|
|
16
16
|
|
|
17
|
+
function publishPackage(dir) {
|
|
18
|
+
runCommand('npm', ['publish'], { cwd: dir, stdio: 'pipe' });
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
function git(dir, args) {
|
|
18
22
|
// stderr is dropped on purpose: `rev-parse` on a tag that does not exist yet is
|
|
19
23
|
// an expected miss, and git prints "fatal: ambiguous argument" for it, which
|
|
20
24
|
// reads like a failure in the middle of a successful release.
|
|
21
|
-
return
|
|
25
|
+
return runCommand('git', args, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export function tagName(version, prefix = 'plugin-v') {
|
|
@@ -51,7 +55,7 @@ export function dirtyPaths(dir, limit = 3, run) {
|
|
|
51
55
|
* anything runs is what makes `--dry-run` useful: it shows the whole sequence
|
|
52
56
|
* and the exact commands.
|
|
53
57
|
*/
|
|
54
|
-
export function plan({ dir = process.cwd(), version, name, tagPrefix = 'plugin-v', indexFile = 'market/index.json' }) {
|
|
58
|
+
export function plan({ dir = process.cwd(), version, name, tagPrefix = 'plugin-v', indexFile = 'market/index.json', marketDir }) {
|
|
55
59
|
const report = doctor(dir);
|
|
56
60
|
const resolvedName = name ?? report.manifest.name ?? 'this-plugin';
|
|
57
61
|
const resolvedVersion = version ?? report.manifest.version ?? '0.0.0';
|
|
@@ -61,7 +65,13 @@ export function plan({ dir = process.cwd(), version, name, tagPrefix = 'plugin-v
|
|
|
61
65
|
{ title: `Create the tag ${tag}`, command: `git tag ${tag}`, runs: true },
|
|
62
66
|
{ title: 'Push the tag, which starts the release workflow', command: `git push origin ${tag}`, runs: true },
|
|
63
67
|
{ title: `Wait until the registry serves ${resolvedName}@${resolvedVersion}`, command: `GET /${resolvedName}`, runs: true },
|
|
64
|
-
{
|
|
68
|
+
{
|
|
69
|
+
title: marketDir === undefined
|
|
70
|
+
? 'Write the version into the market index, with the publish time the registry reported'
|
|
71
|
+
: `Write the version into the market index at ${marketDir}`,
|
|
72
|
+
command: `patch ${path.join(marketDir ?? '', indexFile)}`,
|
|
73
|
+
runs: true,
|
|
74
|
+
},
|
|
65
75
|
{ title: 'Open the index pull request', command: 'gh pr create', runs: false },
|
|
66
76
|
{ title: 'Re-read the live index and confirm the version is listed', command: 'GET market/index.json', runs: false },
|
|
67
77
|
];
|
|
@@ -74,6 +84,7 @@ export async function release(options) {
|
|
|
74
84
|
registry,
|
|
75
85
|
tagPrefix = 'plugin-v',
|
|
76
86
|
indexFile = 'market/index.json',
|
|
87
|
+
marketDir,
|
|
77
88
|
budgetMs,
|
|
78
89
|
intervalMs,
|
|
79
90
|
sleep,
|
|
@@ -115,6 +126,17 @@ export async function release(options) {
|
|
|
115
126
|
gitRun(['tag', tag]);
|
|
116
127
|
gitRun(['push', 'origin', tag]);
|
|
117
128
|
|
|
129
|
+
// Not every plugin publishes from CI. `--publish` runs the publish here, so
|
|
130
|
+
// the same command covers both kinds of repository.
|
|
131
|
+
if (options.publish === true) {
|
|
132
|
+
try {
|
|
133
|
+
(options.publishPackage ?? publishPackage)(dir);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const detail = String(error.stderr ?? error.message).trim().split('\n').slice(-1)[0];
|
|
136
|
+
return { ok: false, stage: 'publish', failures: [{ check: 'publish', message: `npm publish failed: ${detail}` }] };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
118
140
|
const waited = await waitForVersion(name, version, {
|
|
119
141
|
// Wait for the publish *time* as well: `versions` can appear a moment before
|
|
120
142
|
// `time`, and an index entry with a null timestamp fails the market checker.
|
|
@@ -155,7 +177,10 @@ export async function release(options) {
|
|
|
155
177
|
|
|
156
178
|
const timestamp = publishedAt(waited.document, version);
|
|
157
179
|
const entry = buildEntry({ version, publishTimestamp: timestamp, hostRange: report.notes.hostRange });
|
|
158
|
-
|
|
180
|
+
// Most plugins are listed in someone else's index: `--market` points at a
|
|
181
|
+
// checkout of that repository, and the entry plus the pull request land there.
|
|
182
|
+
const indexRoot = typeof marketDir === 'string' && marketDir.length > 0 ? path.resolve(marketDir) : dir;
|
|
183
|
+
const indexPath = path.join(indexRoot, indexFile);
|
|
159
184
|
let indexUpdate = null;
|
|
160
185
|
if (fs.existsSync(indexPath) === true) {
|
|
161
186
|
try {
|
|
@@ -163,10 +188,10 @@ export async function release(options) {
|
|
|
163
188
|
const after = addVersion(before, name, entry);
|
|
164
189
|
if (after !== before) {
|
|
165
190
|
writeIndex(indexPath, after);
|
|
166
|
-
indexUpdate = { file:
|
|
191
|
+
indexUpdate = { file: indexPath, version, entry };
|
|
167
192
|
if (typeof onChange === 'function') onChange(indexUpdate);
|
|
168
193
|
} else {
|
|
169
|
-
indexUpdate = { file:
|
|
194
|
+
indexUpdate = { file: indexPath, version, entry, unchanged: true };
|
|
170
195
|
}
|
|
171
196
|
} catch (error) {
|
|
172
197
|
return { ok: false, stage: 'index', failures: [{ check: 'index', message: error.message }] };
|
|
@@ -188,16 +213,17 @@ export async function release(options) {
|
|
|
188
213
|
// The index change is one line, but it still needs a branch, a pull request,
|
|
189
214
|
// a green CI and a merge; doing that by hand is where "forgot to open the PR"
|
|
190
215
|
// happens.
|
|
191
|
-
const ghRunner = options.gh ?? defaultGh(
|
|
216
|
+
const ghRunner = options.gh ?? defaultGh(indexRoot);
|
|
217
|
+
const marketGit = options.marketGit ?? (indexRoot === dir ? options.git : ((args) => git(indexRoot, args)));
|
|
192
218
|
const pull = await openIndexPr({
|
|
193
|
-
dir,
|
|
219
|
+
dir: indexRoot,
|
|
220
|
+
git: marketGit,
|
|
194
221
|
indexFile,
|
|
195
222
|
version,
|
|
196
223
|
base: options.base ?? 'main',
|
|
197
224
|
branch: options.branch,
|
|
198
225
|
title: options.prTitle,
|
|
199
226
|
body: options.prBody,
|
|
200
|
-
git: options.git,
|
|
201
227
|
gh: ghRunner,
|
|
202
228
|
onStep: options.onStep,
|
|
203
229
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-toolkit",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "Preflight, release and test DeepSeek Harness plugins: checks what the host needs, waits for npm to actually serve the version, and keeps the market index honest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|