dsh-plugin-toolkit 0.3.3 → 0.3.5
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 +14 -0
- package/lib/registry.mjs +12 -2
- package/lib/release.mjs +26 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.5
|
|
4
|
+
|
|
5
|
+
Four bugs found by running `release` for real against a throwaway package, instead of only `--dry-run`:
|
|
6
|
+
|
|
7
|
+
- The index entry came out with `publishTimestamp: null`. The registry was asked for the abbreviated packument (`application/vnd.npm.install-v1+json`), which has no `time` field at all — so the tool could see the version and still have no publish date. It now asks for the full document, and waits for `time[version]` before writing the entry.
|
|
8
|
+
- `git rev-parse` on a tag that does not exist yet printed `fatal: ambiguous argument …` in the middle of a successful release; stderr is now dropped for that call.
|
|
9
|
+
- "the working tree has uncommitted changes" now names up to three paths.
|
|
10
|
+
- A folder without `package.json` raised a stack trace instead of returning a preflight failure.
|
|
11
|
+
|
|
12
|
+
## 0.3.4
|
|
13
|
+
|
|
14
|
+
- CI now runs on Windows as well as Linux and macOS; two tests assumed a POSIX temp path (`/tmp/…`) and were rewritten with `path.join`.
|
|
15
|
+
- Verified `mount`, `doctor` and `release --dry-run` on Node 18.20.8 (not just the version CI installs), and the SSE wait against a stream shaped like the app's.
|
|
16
|
+
|
|
3
17
|
## 0.3.3
|
|
4
18
|
|
|
5
19
|
- `mount` on a path that is missing, or that is a directory, now says what to pass instead of raising `ENOENT: no such file or directory, open '…'`. Found by running the three commands in a clean HOME with a fresh clone.
|
package/lib/registry.mjs
CHANGED
|
@@ -11,8 +11,12 @@ export const REGISTRY = 'https://registry.npmjs.org';
|
|
|
11
11
|
export async function packument(name, options = {}) {
|
|
12
12
|
const base = options.registry ?? REGISTRY;
|
|
13
13
|
const request = options.fetch ?? fetch;
|
|
14
|
+
// The full document, not the abbreviated install one: the index entry needs
|
|
15
|
+
// `time[version]`, and `application/vnd.npm.install-v1+json` omits `time`
|
|
16
|
+
// entirely — which is why an abbreviated fetch can see a version and still
|
|
17
|
+
// have no publish date to write.
|
|
14
18
|
const response = await request(`${base}/${name.replace('/', '%2F')}`, {
|
|
15
|
-
headers: { accept: 'application/
|
|
19
|
+
headers: { accept: 'application/json' },
|
|
16
20
|
});
|
|
17
21
|
if (response.status === 404) return null;
|
|
18
22
|
if (response.ok === false) {
|
|
@@ -40,6 +44,12 @@ export function hasVersion(document, version) {
|
|
|
40
44
|
* `onWait` is called after each miss so the CLI can report progress.
|
|
41
45
|
*/
|
|
42
46
|
export async function waitForVersion(name, version, options = {}) {
|
|
47
|
+
// `requireTime` matters for the market index: `versions` can list a version
|
|
48
|
+
// before the registry has written its `time` entry, and an index entry with a
|
|
49
|
+
// null timestamp is rejected by the checker.
|
|
50
|
+
const ready = options.requireTime === true
|
|
51
|
+
? (document) => hasVersion(document, version) === true && publishedAt(document, version) !== null
|
|
52
|
+
: (document) => hasVersion(document, version) === true;
|
|
43
53
|
const budgetMs = options.budgetMs ?? 8 * 60 * 1000;
|
|
44
54
|
const intervalMs = options.intervalMs ?? 10 * 1000;
|
|
45
55
|
const now = options.now ?? Date.now;
|
|
@@ -50,7 +60,7 @@ export async function waitForVersion(name, version, options = {}) {
|
|
|
50
60
|
for (;;) {
|
|
51
61
|
attempts += 1;
|
|
52
62
|
const document = await packument(name, options);
|
|
53
|
-
if (
|
|
63
|
+
if (ready(document) === true) {
|
|
54
64
|
return { document, attempts, waitedMs: now() - started };
|
|
55
65
|
}
|
|
56
66
|
if (now() - started >= budgetMs) {
|
package/lib/release.mjs
CHANGED
|
@@ -14,7 +14,10 @@ import { addVersion, buildEntry, readIndex, writeIndex } from './index-file.mjs'
|
|
|
14
14
|
import { publishedAt, waitForVersion } from './registry.mjs';
|
|
15
15
|
|
|
16
16
|
function git(dir, args) {
|
|
17
|
-
|
|
17
|
+
// stderr is dropped on purpose: `rev-parse` on a tag that does not exist yet is
|
|
18
|
+
// an expected miss, and git prints "fatal: ambiguous argument" for it, which
|
|
19
|
+
// reads like a failure in the middle of a successful release.
|
|
20
|
+
return execFileSync('git', args, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export function tagName(version, prefix = 'plugin-v') {
|
|
@@ -34,6 +37,14 @@ export function workingTreeIsClean(dir) {
|
|
|
34
37
|
return git(dir, ['status', '--porcelain']) === '';
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/** The paths git would carry into the tag, so the message can name them. */
|
|
41
|
+
export function dirtyPaths(dir, limit = 3) {
|
|
42
|
+
const status = git(dir, ['status', '--porcelain']);
|
|
43
|
+
if (status === '') return [];
|
|
44
|
+
const paths = status.split('\n').map((line) => line.slice(3).trim()).filter((line) => line !== '');
|
|
45
|
+
return paths.slice(0, limit);
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
49
|
* Steps a human must do, and steps the tool can do. Returning this list before
|
|
39
50
|
* anything runs is what makes `--dry-run` useful: it shows the whole sequence
|
|
@@ -69,7 +80,14 @@ export async function release(options) {
|
|
|
69
80
|
onChange,
|
|
70
81
|
} = options;
|
|
71
82
|
|
|
72
|
-
|
|
83
|
+
let report;
|
|
84
|
+
try {
|
|
85
|
+
report = doctor(dir);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// A folder without package.json is a preflight failure, not a crash: the
|
|
88
|
+
// command's job is to say what is wrong, and a stack trace is not an answer.
|
|
89
|
+
return { ok: false, stage: 'preflight', failures: [{ check: 'manifest', message: error.message }] };
|
|
90
|
+
}
|
|
73
91
|
if (report.failures.length > 0) {
|
|
74
92
|
return { ok: false, stage: 'preflight', failures: report.failures };
|
|
75
93
|
}
|
|
@@ -82,10 +100,12 @@ export async function release(options) {
|
|
|
82
100
|
return { ok: false, stage: 'tag', failures: [{ check: 'tag', message: `${tag} already exists` }] };
|
|
83
101
|
}
|
|
84
102
|
if (workingTreeIsClean(dir) === false) {
|
|
103
|
+
const dirty = dirtyPaths(dir);
|
|
104
|
+
const listed = dirty.length > 0 ? ` (${dirty.join(', ')})` : '';
|
|
85
105
|
return {
|
|
86
106
|
ok: false,
|
|
87
107
|
stage: 'git',
|
|
88
|
-
failures: [{ check: 'git', message:
|
|
108
|
+
failures: [{ check: 'git', message: `the working tree has uncommitted changes${listed}` }],
|
|
89
109
|
};
|
|
90
110
|
}
|
|
91
111
|
|
|
@@ -93,6 +113,9 @@ export async function release(options) {
|
|
|
93
113
|
git(dir, ['push', 'origin', tag]);
|
|
94
114
|
|
|
95
115
|
const waited = await waitForVersion(name, version, {
|
|
116
|
+
// Wait for the publish *time* as well: `versions` can appear a moment before
|
|
117
|
+
// `time`, and an index entry with a null timestamp fails the market checker.
|
|
118
|
+
requireTime: true,
|
|
96
119
|
registry,
|
|
97
120
|
budgetMs,
|
|
98
121
|
intervalMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-toolkit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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": {
|