dshmarket 1.2.2 → 1.2.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/src/profile.ts ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Profile filesystem reads — everything the market learns from a dsh
3
+ * profile directory (manifest, lockfile, installed package trees). Pure
4
+ * functions of the directory contents; no processes, no network.
5
+ */
6
+
7
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
8
+ import { homedir } from 'node:os'
9
+ import { join } from 'node:path'
10
+
11
+ /** Resolve a profile name to its directory under DSH_HOME (default ~/.dsh). */
12
+ export function profileDir(profile: string): string {
13
+ const home = process.env.DSH_HOME ?? join(homedir(), '.dsh')
14
+ return join(home, 'profiles', profile)
15
+ }
16
+
17
+ /** Community dependencies of the profile (official in-box scope filtered out). */
18
+ export function readInstalled(profile: string): Record<string, string> {
19
+ try {
20
+ const manifest = JSON.parse(readFileSync(join(profileDir(profile), 'package.json'), 'utf8')) as {
21
+ dependencies?: Record<string, string>
22
+ }
23
+ const installed: Record<string, string> = {}
24
+ for (const [name, spec] of Object.entries(manifest.dependencies ?? {})) {
25
+ if (!name.startsWith('@deepseek-ai/')) installed[name] = spec
26
+ }
27
+ return installed
28
+ } catch {
29
+ return {}
30
+ }
31
+ }
32
+
33
+ /** The version actually present in the profile's node_modules, or null. */
34
+ export function readInstalledVersion(profile: string, name: string): string | null {
35
+ try {
36
+ const manifest = JSON.parse(
37
+ readFileSync(join(profileDir(profile), 'node_modules', name, 'package.json'), 'utf8'),
38
+ ) as { version?: string }
39
+ return manifest.version ?? null
40
+ } catch {
41
+ return null
42
+ }
43
+ }
44
+
45
+ /** Pinned commit per `owner/repo` from the profile lockfile's codeload tarball URLs. */
46
+ export function readLockCommits(profile: string): Map<string, string> {
47
+ const commits = new Map<string, string>()
48
+ try {
49
+ const lock = readFileSync(join(profileDir(profile), 'pnpm-lock.yaml'), 'utf8')
50
+ for (const m of lock.matchAll(/codeload\.github\.com\/([^/\s]+\/[^/\s]+)\/tar\.gz\/([0-9a-f]{40})/g)) {
51
+ commits.set(m[1].toLowerCase(), m[2])
52
+ }
53
+ } catch { /* no lockfile — no git installs to report */ }
54
+ return commits
55
+ }
56
+
57
+ /** True when the installed package's manifest declares a dsh plugin surface. */
58
+ export function hasDshManifest(dir: string): boolean {
59
+ try {
60
+ const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { dsh?: unknown }
61
+ return manifest.dsh !== undefined
62
+ } catch {
63
+ return false
64
+ }
65
+ }
66
+
67
+ /**
68
+ * True when the package's declared entry artifact actually exists — github
69
+ * source checkouts of build-required plugins ship no lib/, and promoting one
70
+ * into the bundle layer bricks the next boot (ERR_MODULE_NOT_FOUND kills the
71
+ * whole profile, #18).
72
+ */
73
+ export function entryArtifactExists(dir: string): boolean {
74
+ try {
75
+ const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as {
76
+ main?: string
77
+ exports?: Record<string, unknown> | string
78
+ }
79
+ const candidates: string[] = []
80
+ if (typeof manifest.main === 'string') candidates.push(manifest.main)
81
+ const rootExport = typeof manifest.exports === 'string'
82
+ ? manifest.exports
83
+ : (manifest.exports as Record<string, unknown> | undefined)?.['.']
84
+ if (typeof rootExport === 'string') candidates.push(rootExport)
85
+ else if (rootExport !== null && typeof rootExport === 'object') {
86
+ for (const value of Object.values(rootExport)) if (typeof value === 'string') candidates.push(value)
87
+ }
88
+ if (candidates.length === 0) candidates.push('index.js')
89
+ return candidates.some(rel => existsSync(join(dir, rel)))
90
+ } catch {
91
+ return false
92
+ }
93
+ }
94
+
95
+ /** Plugin subdirectories (depth 2) of a collection checkout, as relative paths. */
96
+ export function pluginSubdirs(root: string): string[] {
97
+ const found: string[] = []
98
+ let level1: string[] = []
99
+ try {
100
+ level1 = readdirSync(root, { withFileTypes: true })
101
+ .filter(dirent => dirent.isDirectory() && /^[A-Za-z0-9_.-]+$/.test(dirent.name) && dirent.name !== 'node_modules')
102
+ .map(dirent => dirent.name)
103
+ } catch {
104
+ return found
105
+ }
106
+ for (const sub of level1) {
107
+ if (hasDshManifest(join(root, sub))) {
108
+ found.push(sub)
109
+ continue
110
+ }
111
+ try {
112
+ for (const inner of readdirSync(join(root, sub), { withFileTypes: true })) {
113
+ if (!inner.isDirectory() || !/^[A-Za-z0-9_.-]+$/.test(inner.name) || inner.name === 'node_modules') continue
114
+ if (hasDshManifest(join(root, sub, inner.name))) found.push(`${sub}/${inner.name}`)
115
+ }
116
+ } catch { /* unreadable level — skip */ }
117
+ if (found.length >= 8) break
118
+ }
119
+ return found.slice(0, 8)
120
+ }