dshmarket 1.66.41 → 1.66.42
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/lib/changelog.js +33 -3
- package/lib/install.js +4 -1
- package/lib/profile.js +37 -5
- package/lib/types/changelog.d.ts +9 -0
- package/lib/types/profile.d.ts +1 -1
- package/package.json +1 -1
- package/src/changelog.ts +33 -3
- package/src/install.ts +4 -1
- package/src/profile.ts +36 -6
package/lib/changelog.js
CHANGED
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
import { fileFromTarball } from './catalog-npm.js';
|
|
24
24
|
import { marketFetch } from './net.js';
|
|
25
25
|
import { activeRegion, routesFor } from './regions.js';
|
|
26
|
-
import { profileDir, readGitResolutionCommit, readInstalled, readInstalledRepoEvidence, readLockCommits } from './profile.js';
|
|
26
|
+
import { profileDir, readGitResolutionCommit, readInstalled, readInstalledRepoEvidence, readInstalledVersion, readLockCommits } from './profile.js';
|
|
27
27
|
import { githubRepoOfRemote, hostedRepoKey, isGitHostedSpec, lookupRepoFromUrl, parseHostShorthand, parseSourceUrl, repoOf, repoOfTarget } from './sources.js';
|
|
28
|
-
import { checkUpdates } from './updates.js';
|
|
28
|
+
import { checkUpdates, compareVersions } from './updates.js';
|
|
29
29
|
import { loadRegistry } from './registry.js';
|
|
30
30
|
const UPDATES_PACKAGE = 'dsh-plugin-updates';
|
|
31
31
|
const UPDATES_FILE = 'package/updates.json';
|
|
@@ -118,6 +118,20 @@ export function sliceCommitsAt(items, installed) {
|
|
|
118
118
|
}
|
|
119
119
|
return { items: out, found: false };
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The version a release tag names, when it names one.
|
|
123
|
+
*
|
|
124
|
+
* Tags come as `v1.2.3`, `1.2.3`, `@scope/pkg@1.2.3` or `pkg-v1.2.3`; the
|
|
125
|
+
* FIRST semver-shaped run is the version in all of them. A tag that names no
|
|
126
|
+
* version (`nightly`) returns null, and callers must treat "unknown" as "do
|
|
127
|
+
* not judge" rather than "older".
|
|
128
|
+
*/
|
|
129
|
+
export function releaseVersionOf(tag) {
|
|
130
|
+
if (tag === null)
|
|
131
|
+
return null;
|
|
132
|
+
const match = /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(tag);
|
|
133
|
+
return match?.[1] ?? null;
|
|
134
|
+
}
|
|
121
135
|
/** The repo url key an entry's update notes live under, or null. */
|
|
122
136
|
export function repoKeyOf(spec) {
|
|
123
137
|
const repo = githubIdentityOf(spec)?.split('#')[0] ?? null;
|
|
@@ -407,7 +421,23 @@ export async function updateNotesFor(profile, explicitDir, name) {
|
|
|
407
421
|
current = statuses?.[name]?.current ?? null;
|
|
408
422
|
}
|
|
409
423
|
if (entry?.release !== undefined && entry.release !== null && (entry.release.body !== '' || entry.release.tag !== null)) {
|
|
410
|
-
|
|
424
|
+
// The release feed is a DAILY snapshot while the update target is read
|
|
425
|
+
// live from npm, so the snapshot's newest release is often the version
|
|
426
|
+
// already installed (or older). Showing it next to "1.66.39 → 1.66.40"
|
|
427
|
+
// describes a change the user already has (#925). When the release
|
|
428
|
+
// names a version no newer than the installed one AND a better tier
|
|
429
|
+
// exists, take the better tier: the commit tail is sliced at this
|
|
430
|
+
// install's sha, and npm publish times are newer than the install by
|
|
431
|
+
// construction. With nothing better, the stale release still beats an
|
|
432
|
+
// empty dialog.
|
|
433
|
+
const installed = readInstalledVersion(profile, name, activeProfileDir);
|
|
434
|
+
const released = releaseVersionOf(entry.release.tag);
|
|
435
|
+
const behind = installed !== null && released !== null
|
|
436
|
+
&& (compareVersions(released, installed) ?? 0) <= 0;
|
|
437
|
+
const hasCommits = (entry.commits?.length ?? 0) > 0;
|
|
438
|
+
if (!(behind && (hasCommits || (specKey === null && npmName !== null)))) {
|
|
439
|
+
return { kind: 'release', release: entry.release };
|
|
440
|
+
}
|
|
411
441
|
}
|
|
412
442
|
if (entry?.commits !== undefined && entry.commits.length > 0) {
|
|
413
443
|
return { kind: 'commits', commits: sliceCommitsAt(entry.commits, current) };
|
package/lib/install.js
CHANGED
|
@@ -662,7 +662,10 @@ export async function validateAddedPlugins(run, profile, before, explicitDir, ho
|
|
|
662
662
|
const packageDir = join(dir, 'node_modules', n);
|
|
663
663
|
// hasLoadableEntry, not entryArtifactExists: carrier bundles legitimately
|
|
664
664
|
// ship no entry of their own (#103) and must not be uninstalled here.
|
|
665
|
-
|
|
665
|
+
// The host install tree is passed too (#906): a carrier mounting only
|
|
666
|
+
// in-box packages resolves nowhere under the profile, and judging it by
|
|
667
|
+
// that alone deleted a package the host had just installed correctly.
|
|
668
|
+
if (!hasDshManifest(packageDir) || !hasLoadableEntry(dir, n, hostDirectory ?? findDshInstallDir())) {
|
|
666
669
|
removedBroken.push(n);
|
|
667
670
|
await removeAndReconcile(run, profile, dir, n, hostDirectory);
|
|
668
671
|
continue;
|
package/lib/profile.js
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* functions of the directory contents; no processes, no network.
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
7
8
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
8
9
|
import { isDeepStrictEqual } from 'node:util';
|
|
9
10
|
import { JSON_SCHEMA, load } from 'js-yaml';
|
|
11
|
+
import { findDshInstallDir } from './dsh-install.js';
|
|
10
12
|
import { resolveDshHome } from './home-paths.js';
|
|
11
13
|
import { githubRemoteIdentities, githubRepoIdentities, hostedRepoKey, isGenerationLink, isGitHostedSpec, repoOfTarget } from './sources.js';
|
|
12
14
|
/**
|
|
@@ -1296,7 +1298,7 @@ export function conflictingEntryIds(profileDirectory, candidate, installedBundle
|
|
|
1296
1298
|
* @param profileDirectory - resolved profile directory (host-authoritative under Desktop).
|
|
1297
1299
|
* @param name - installed package name.
|
|
1298
1300
|
*/
|
|
1299
|
-
export function hasLoadableEntry(profileDirectory, name) {
|
|
1301
|
+
export function hasLoadableEntry(profileDirectory, name, hostDirectory) {
|
|
1300
1302
|
const dir = join(profileDirectory, 'node_modules', name);
|
|
1301
1303
|
if (entryArtifactExists(dir))
|
|
1302
1304
|
return true;
|
|
@@ -1310,11 +1312,41 @@ export function hasLoadableEntry(profileDirectory, name) {
|
|
|
1310
1312
|
// exiting 0 was immediately followed by the market removing what it had
|
|
1311
1313
|
// just, correctly, installed.
|
|
1312
1314
|
const workspaceRoot = dirname(profileDirectory);
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
.some(target => entryArtifactExists(join(profileDirectory, 'node_modules', target))
|
|
1315
|
+
const targets = bundlePatchTargets(dir).filter(target => target !== name);
|
|
1316
|
+
if (targets.some(target => entryArtifactExists(join(profileDirectory, 'node_modules', target))
|
|
1316
1317
|
|| entryArtifactExists(join(dir, 'node_modules', target))
|
|
1317
|
-
|| entryArtifactExists(join(workspaceRoot, 'node_modules', target)))
|
|
1318
|
+
|| entryArtifactExists(join(workspaceRoot, 'node_modules', target))))
|
|
1319
|
+
return true;
|
|
1320
|
+
// #906: the host's OWN packages live in its install tree, not in any
|
|
1321
|
+
// profile node_modules — `<install>/dsh/node_modules/...`, often inside
|
|
1322
|
+
// app.asar. A carrier whose patch mounts nothing but in-box packages
|
|
1323
|
+
// (@deepseek-ai/dsh-mcp-client and the like) therefore failed every root
|
|
1324
|
+
// above, was reported "broken", and — through validateAddedPlugins — was
|
|
1325
|
+
// uninstalled right after a successful install. `undefined` resolves the
|
|
1326
|
+
// install dir lazily (only carriers pay for it); `null` means "do not look".
|
|
1327
|
+
//
|
|
1328
|
+
// Inside app.asar the probe is blind for EVERY @deepseek-ai/ name (the
|
|
1329
|
+
// same measurement behind #676), so there identity decides instead of the
|
|
1330
|
+
// filesystem — the host publishes that scope, and our own composer loads
|
|
1331
|
+
// those packages from the archive at boot.
|
|
1332
|
+
const host = hostDirectory === undefined ? findDshInstallDir() : hostDirectory;
|
|
1333
|
+
if (host === null || targets.length === 0)
|
|
1334
|
+
return false;
|
|
1335
|
+
const packaged = /[/\\]app\.asar[/\\]/iu.test(host);
|
|
1336
|
+
return targets.some(target => packaged && target.startsWith('@deepseek-ai/')
|
|
1337
|
+
|| packageResolvableFrom(join(host, 'package.json'), target));
|
|
1338
|
+
}
|
|
1339
|
+
/** Whether `target` resolves from the host's own dependency tree. */
|
|
1340
|
+
function packageResolvableFrom(anchorPackageJson, target) {
|
|
1341
|
+
try {
|
|
1342
|
+
const paths = createRequire(anchorPackageJson).resolve.paths(target) ?? [];
|
|
1343
|
+
return paths.some(searchPath => existsSync(join(searchPath, target, 'package.json')));
|
|
1344
|
+
}
|
|
1345
|
+
catch {
|
|
1346
|
+
// An unreadable anchor says nothing about the target: treat as absent
|
|
1347
|
+
// rather than loadable, so the caller keeps its conservative answer.
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1318
1350
|
}
|
|
1319
1351
|
/** Plugin subdirectories (depth 2) of a collection checkout, as relative paths. */
|
|
1320
1352
|
export function pluginSubdirs(root) {
|
package/lib/types/changelog.d.ts
CHANGED
|
@@ -63,6 +63,15 @@ export declare function sliceCommitsAt(items: CommitNote[], installed: string |
|
|
|
63
63
|
items: CommitNote[];
|
|
64
64
|
found: boolean;
|
|
65
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* The version a release tag names, when it names one.
|
|
68
|
+
*
|
|
69
|
+
* Tags come as `v1.2.3`, `1.2.3`, `@scope/pkg@1.2.3` or `pkg-v1.2.3`; the
|
|
70
|
+
* FIRST semver-shaped run is the version in all of them. A tag that names no
|
|
71
|
+
* version (`nightly`) returns null, and callers must treat "unknown" as "do
|
|
72
|
+
* not judge" rather than "older".
|
|
73
|
+
*/
|
|
74
|
+
export declare function releaseVersionOf(tag: string | null): string | null;
|
|
66
75
|
/** The repo url key an entry's update notes live under, or null. */
|
|
67
76
|
export declare function repoKeyOf(spec: string): string | null;
|
|
68
77
|
/**
|
package/lib/types/profile.d.ts
CHANGED
|
@@ -412,7 +412,7 @@ export declare function conflictingEntryIds(profileDirectory: string, candidate:
|
|
|
412
412
|
* @param profileDirectory - resolved profile directory (host-authoritative under Desktop).
|
|
413
413
|
* @param name - installed package name.
|
|
414
414
|
*/
|
|
415
|
-
export declare function hasLoadableEntry(profileDirectory: string, name: string): boolean;
|
|
415
|
+
export declare function hasLoadableEntry(profileDirectory: string, name: string, hostDirectory?: string | null): boolean;
|
|
416
416
|
/** Plugin subdirectories (depth 2) of a collection checkout, as relative paths. */
|
|
417
417
|
export declare function pluginSubdirs(root: string): string[];
|
|
418
418
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dshmarket",
|
|
3
3
|
"description": "Visual plugin market inside DeepSeek Harness — browse, search, and one-click install community plugins. · DSH 可视化插件市场:逛一逛,点一下,装好。",
|
|
4
|
-
"version": "1.66.
|
|
4
|
+
"version": "1.66.42",
|
|
5
5
|
"icon": "assets/logo.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
package/src/changelog.ts
CHANGED
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
import { fileFromTarball } from './catalog-npm.ts'
|
|
25
25
|
import { marketFetch } from './net.ts'
|
|
26
26
|
import { activeRegion, routesFor } from './regions.ts'
|
|
27
|
-
import { profileDir, readGitResolutionCommit, readInstalled, readInstalledRepoEvidence, readLockCommits } from './profile.ts'
|
|
27
|
+
import { profileDir, readGitResolutionCommit, readInstalled, readInstalledRepoEvidence, readInstalledVersion, readLockCommits } from './profile.ts'
|
|
28
28
|
import { githubRepoOfRemote, hostedRepoKey, isGitHostedSpec, lookupRepoFromUrl, parseHostShorthand, parseSourceUrl, repoOf, repoOfTarget } from './sources.ts'
|
|
29
|
-
import { checkUpdates } from './updates.ts'
|
|
29
|
+
import { checkUpdates, compareVersions } from './updates.ts'
|
|
30
30
|
import { loadRegistry, type RegistryPlugin } from './registry.ts'
|
|
31
31
|
|
|
32
32
|
const UPDATES_PACKAGE = 'dsh-plugin-updates'
|
|
@@ -141,6 +141,20 @@ export function sliceCommitsAt(items: CommitNote[], installed: string | null): {
|
|
|
141
141
|
return { items: out, found: false }
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* The version a release tag names, when it names one.
|
|
146
|
+
*
|
|
147
|
+
* Tags come as `v1.2.3`, `1.2.3`, `@scope/pkg@1.2.3` or `pkg-v1.2.3`; the
|
|
148
|
+
* FIRST semver-shaped run is the version in all of them. A tag that names no
|
|
149
|
+
* version (`nightly`) returns null, and callers must treat "unknown" as "do
|
|
150
|
+
* not judge" rather than "older".
|
|
151
|
+
*/
|
|
152
|
+
export function releaseVersionOf(tag: string | null): string | null {
|
|
153
|
+
if (tag === null) return null
|
|
154
|
+
const match = /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(tag)
|
|
155
|
+
return match?.[1] ?? null
|
|
156
|
+
}
|
|
157
|
+
|
|
144
158
|
/** The repo url key an entry's update notes live under, or null. */
|
|
145
159
|
export function repoKeyOf(spec: string): string | null {
|
|
146
160
|
const repo = githubIdentityOf(spec)?.split('#')[0] ?? null
|
|
@@ -450,7 +464,23 @@ export async function updateNotesFor(
|
|
|
450
464
|
}
|
|
451
465
|
|
|
452
466
|
if (entry?.release !== undefined && entry.release !== null && (entry.release.body !== '' || entry.release.tag !== null)) {
|
|
453
|
-
|
|
467
|
+
// The release feed is a DAILY snapshot while the update target is read
|
|
468
|
+
// live from npm, so the snapshot's newest release is often the version
|
|
469
|
+
// already installed (or older). Showing it next to "1.66.39 → 1.66.40"
|
|
470
|
+
// describes a change the user already has (#925). When the release
|
|
471
|
+
// names a version no newer than the installed one AND a better tier
|
|
472
|
+
// exists, take the better tier: the commit tail is sliced at this
|
|
473
|
+
// install's sha, and npm publish times are newer than the install by
|
|
474
|
+
// construction. With nothing better, the stale release still beats an
|
|
475
|
+
// empty dialog.
|
|
476
|
+
const installed = readInstalledVersion(profile, name, activeProfileDir)
|
|
477
|
+
const released = releaseVersionOf(entry.release.tag)
|
|
478
|
+
const behind = installed !== null && released !== null
|
|
479
|
+
&& (compareVersions(released, installed) ?? 0) <= 0
|
|
480
|
+
const hasCommits = (entry.commits?.length ?? 0) > 0
|
|
481
|
+
if (!(behind && (hasCommits || (specKey === null && npmName !== null)))) {
|
|
482
|
+
return { kind: 'release', release: entry.release }
|
|
483
|
+
}
|
|
454
484
|
}
|
|
455
485
|
if (entry?.commits !== undefined && entry.commits.length > 0) {
|
|
456
486
|
return { kind: 'commits', commits: sliceCommitsAt(entry.commits, current) }
|
package/src/install.ts
CHANGED
|
@@ -659,7 +659,10 @@ export async function validateAddedPlugins(
|
|
|
659
659
|
const packageDir = join(dir, 'node_modules', n)
|
|
660
660
|
// hasLoadableEntry, not entryArtifactExists: carrier bundles legitimately
|
|
661
661
|
// ship no entry of their own (#103) and must not be uninstalled here.
|
|
662
|
-
|
|
662
|
+
// The host install tree is passed too (#906): a carrier mounting only
|
|
663
|
+
// in-box packages resolves nowhere under the profile, and judging it by
|
|
664
|
+
// that alone deleted a package the host had just installed correctly.
|
|
665
|
+
if (!hasDshManifest(packageDir) || !hasLoadableEntry(dir, n, hostDirectory ?? findDshInstallDir())) {
|
|
663
666
|
removedBroken.push(n)
|
|
664
667
|
await removeAndReconcile(run, profile, dir, n, hostDirectory)
|
|
665
668
|
continue
|
package/src/profile.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
|
8
|
+
import { createRequire } from 'node:module'
|
|
8
9
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
9
10
|
import { isDeepStrictEqual } from 'node:util'
|
|
10
11
|
import { JSON_SCHEMA, load } from 'js-yaml'
|
|
12
|
+
import { findDshInstallDir } from './dsh-install.ts'
|
|
11
13
|
import { resolveDshHome } from './home-paths.ts'
|
|
12
14
|
import { githubRemoteIdentities, githubRepoIdentities, hostedRepoKey, isGenerationLink, isGitHostedSpec, repoOfTarget } from './sources.ts'
|
|
13
15
|
|
|
@@ -1340,7 +1342,7 @@ export function conflictingEntryIds(
|
|
|
1340
1342
|
* @param profileDirectory - resolved profile directory (host-authoritative under Desktop).
|
|
1341
1343
|
* @param name - installed package name.
|
|
1342
1344
|
*/
|
|
1343
|
-
export function hasLoadableEntry(profileDirectory: string, name: string): boolean {
|
|
1345
|
+
export function hasLoadableEntry(profileDirectory: string, name: string, hostDirectory?: string | null): boolean {
|
|
1344
1346
|
const dir = join(profileDirectory, 'node_modules', name)
|
|
1345
1347
|
if (entryArtifactExists(dir)) return true
|
|
1346
1348
|
// A carrier is only sound when something it mounts is itself loadable.
|
|
@@ -1353,11 +1355,39 @@ export function hasLoadableEntry(profileDirectory: string, name: string): boolea
|
|
|
1353
1355
|
// exiting 0 was immediately followed by the market removing what it had
|
|
1354
1356
|
// just, correctly, installed.
|
|
1355
1357
|
const workspaceRoot = dirname(profileDirectory)
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1358
|
+
const targets = bundlePatchTargets(dir).filter(target => target !== name)
|
|
1359
|
+
if (targets.some(target => entryArtifactExists(join(profileDirectory, 'node_modules', target))
|
|
1360
|
+
|| entryArtifactExists(join(dir, 'node_modules', target))
|
|
1361
|
+
|| entryArtifactExists(join(workspaceRoot, 'node_modules', target)))) return true
|
|
1362
|
+
// #906: the host's OWN packages live in its install tree, not in any
|
|
1363
|
+
// profile node_modules — `<install>/dsh/node_modules/...`, often inside
|
|
1364
|
+
// app.asar. A carrier whose patch mounts nothing but in-box packages
|
|
1365
|
+
// (@deepseek-ai/dsh-mcp-client and the like) therefore failed every root
|
|
1366
|
+
// above, was reported "broken", and — through validateAddedPlugins — was
|
|
1367
|
+
// uninstalled right after a successful install. `undefined` resolves the
|
|
1368
|
+
// install dir lazily (only carriers pay for it); `null` means "do not look".
|
|
1369
|
+
//
|
|
1370
|
+
// Inside app.asar the probe is blind for EVERY @deepseek-ai/ name (the
|
|
1371
|
+
// same measurement behind #676), so there identity decides instead of the
|
|
1372
|
+
// filesystem — the host publishes that scope, and our own composer loads
|
|
1373
|
+
// those packages from the archive at boot.
|
|
1374
|
+
const host = hostDirectory === undefined ? findDshInstallDir() : hostDirectory
|
|
1375
|
+
if (host === null || targets.length === 0) return false
|
|
1376
|
+
const packaged = /[/\\]app\.asar[/\\]/iu.test(host)
|
|
1377
|
+
return targets.some(target => packaged && target.startsWith('@deepseek-ai/')
|
|
1378
|
+
|| packageResolvableFrom(join(host, 'package.json'), target))
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/** Whether `target` resolves from the host's own dependency tree. */
|
|
1382
|
+
function packageResolvableFrom(anchorPackageJson: string, target: string): boolean {
|
|
1383
|
+
try {
|
|
1384
|
+
const paths = createRequire(anchorPackageJson).resolve.paths(target) ?? []
|
|
1385
|
+
return paths.some(searchPath => existsSync(join(searchPath, target, 'package.json')))
|
|
1386
|
+
} catch {
|
|
1387
|
+
// An unreadable anchor says nothing about the target: treat as absent
|
|
1388
|
+
// rather than loadable, so the caller keeps its conservative answer.
|
|
1389
|
+
return false
|
|
1390
|
+
}
|
|
1361
1391
|
}
|
|
1362
1392
|
|
|
1363
1393
|
/** Plugin subdirectories (depth 2) of a collection checkout, as relative paths. */
|