dsh-plugin 1.4.0 → 1.4.1
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 +12 -12
- package/README.md +12 -12
- package/client/client.js +308 -303
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +43 -2
- package/lib/services/install/preflight.js +6 -6
- package/lib/types/services/install/preflight.d.ts +5 -1
- package/package.json +1 -1
- package/src/client/components/catalog/CatalogList.tsx +2 -2
- package/src/client/components/layout/CatalogHeader.tsx +5 -4
- package/src/client/logic/urls.ts +8 -2
- package/src/server/http/routes.ts +44 -2
- package/src/server/services/install/preflight.ts +11 -7
package/lib/http/routes.js
CHANGED
|
@@ -121,6 +121,20 @@ export function readInstalled(profile) {
|
|
|
121
121
|
return {};
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
/** 读已装依赖实际落在磁盘上的仓库身份(`owner/repo`):解析 node_modules/<name>/package.json
|
|
125
|
+
* 的 repository 字段。已装 spec 可能是纯版本号(丢了仓库身份),但真实包元数据仍指向来源仓库,
|
|
126
|
+
* 据此可区分「同名依赖其实是同一仓库的残留(应转 update)」还是「跨仓库包名冲突(应报错)」。
|
|
127
|
+
* 读不到 / 解析不出返回 null(交给调用方按冲突处理)。 */
|
|
128
|
+
function installedRepoOf(profile, name) {
|
|
129
|
+
try {
|
|
130
|
+
const pkg = JSON.parse(readFileSync(join(profileDirectory(profile), 'node_modules', name, 'package.json'), 'utf8'));
|
|
131
|
+
const repo = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
|
|
132
|
+
return repo ? githubRepoOf(repo) : null;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
124
138
|
/** 宿主 dsh CLI 版本:优先直接跑 `dsh --version`(与 pnpm/npm/git 同款同步探测,1500ms 超时兜底,
|
|
125
139
|
* 绝不挂起)—— 原先从 process.argv[1] 向上找 package.json 猜版本在部分宿主加载方式下取不到,
|
|
126
140
|
* 导致 issue 环境快照 DSH: unknown;拿不到再退回入口 package.json 查找(dsh 不在 PATH 时兜底)。 */
|
|
@@ -655,8 +669,9 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
655
669
|
// 任务自动入队:即使已有插件操作在跑也接受请求(FIFO 串行执行),不再 409 拒绝
|
|
656
670
|
try {
|
|
657
671
|
const body = await readJsonBody(request);
|
|
658
|
-
// mode: 'update' = 已安装目标的覆盖更新(放行 add,pnpm
|
|
659
|
-
|
|
672
|
+
// mode: 'update' = 已安装目标的覆盖更新(放行 add,pnpm 对已存在依赖原位覆盖重装)。
|
|
673
|
+
// 后续「同仓库残留」检测可能把 install 就地升级为 update(见包名冲突检测),故用 let。
|
|
674
|
+
let mode = body !== null && typeof body === 'object' && body.mode === 'update'
|
|
660
675
|
? 'update'
|
|
661
676
|
: 'install';
|
|
662
677
|
// installTargetOf 先把完整 DSH 命令(`dsh plugin [--profile <p>] add <target>`)剥成裸目标,
|
|
@@ -829,6 +844,32 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
|
|
|
829
844
|
});
|
|
830
845
|
return;
|
|
831
846
|
}
|
|
847
|
+
// 包名冲突检测(仅 git 通道):git 目标包内声明的 name 若与 profile 已装依赖同名,
|
|
848
|
+
// pnpm 会以该 name 做依赖键撞车,抛出的 CLI 报错晦涩难懂(issue #25:sandbase-harness
|
|
849
|
+
// 声明 managed-agents,但该包名在 registry 上被另一仓库占用,npm 反查搜不到 → git 直装
|
|
850
|
+
// 撞已装同名依赖)。到这里已装依赖要么来源不同仓库、要么 spec 是版本号解析不出仓库身份。
|
|
851
|
+
// 读 node_modules 里已装包的真实 repository 区分两种情况:
|
|
852
|
+
// - 指向同一仓库 → 是「同仓库残留」(spec 丢了仓库身份),就地转 update 覆盖重装(幂等);
|
|
853
|
+
// - 指向别的仓库 / 读不到 → 跨仓库包名冲突,入队前转成明确 409,指导用户先卸载或改装。
|
|
854
|
+
if (preflight.name !== null && repoTarget !== null && target === repoTarget) {
|
|
855
|
+
const declared = preflight.name;
|
|
856
|
+
if (installed[declared] !== undefined) {
|
|
857
|
+
const installedRepo = installedRepoOf(profile, declared);
|
|
858
|
+
if (installedRepo !== null && gitRepo !== null && installedRepo.toLowerCase() === gitRepo.toLowerCase()) {
|
|
859
|
+
// 同名且同源:已装 spec 丢了仓库身份导致的残留,直接按 update 覆盖重装,不打扰用户
|
|
860
|
+
attempts.push(`same-repo residual: \`${declared}\` resolves to \`${installedRepo}\` → reinstall as update`);
|
|
861
|
+
mode = 'update';
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
attempts.push(`package name conflict: \`${gitRepo}\` declares npm name \`${declared}\`, which is already present in the profile and cannot be matched to this repo (that npm name is likely registered by another repository)`);
|
|
865
|
+
const error = lang === 'zh'
|
|
866
|
+
? `already installed: ${declared} —— ${gitRepo} 声明的 npm 包名「${declared}」已存在于当前 profile,且无法匹配到本仓库(该包名在 npm 上可能被其他仓库占用)。请先移除现有的 ${declared}(dsh plugin remove ${declared}),再重新安装;或改装该包名对应的官方 npm 包。`
|
|
867
|
+
: `already installed: ${declared} — ${gitRepo} declares the npm package name "${declared}", which is already present in the profile and cannot be matched to this repo (that npm name is likely registered by another repository). Remove the existing ${declared} dependency (dsh plugin remove ${declared}) first, then retry; or install the official npm package under that name.`;
|
|
868
|
+
sendJson(response, 409, { error, attempts });
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
832
873
|
}
|
|
833
874
|
// 入队前复检(与上方检查同语义,但必须在同步块内):preflight 的 await 期间并发请求
|
|
834
875
|
// 可能已通过上方检查、任务却尚未入队——此处检查与 startPluginMutation 之间无 await,
|
|
@@ -25,7 +25,7 @@ const PREFLIGHT_TIMEOUT_MS = 15_000;
|
|
|
25
25
|
export async function preflightTarget(target) {
|
|
26
26
|
const source = githubRepoOf(target);
|
|
27
27
|
if (source === null)
|
|
28
|
-
return { ok: true, missing: null };
|
|
28
|
+
return { ok: true, missing: null, name: null };
|
|
29
29
|
const [owner, repo] = source.split('/');
|
|
30
30
|
let commit = '';
|
|
31
31
|
try {
|
|
@@ -36,17 +36,17 @@ export async function preflightTarget(target) {
|
|
|
36
36
|
commit = stdout.split(/\s+/)[0] ?? '';
|
|
37
37
|
}
|
|
38
38
|
catch {
|
|
39
|
-
return { ok: true, missing: null };
|
|
39
|
+
return { ok: true, missing: null, name: null };
|
|
40
40
|
}
|
|
41
41
|
if (!commit)
|
|
42
|
-
return { ok: true, missing: null };
|
|
42
|
+
return { ok: true, missing: null, name: null };
|
|
43
43
|
const dir = mkdtempSync(join(tmpdir(), 'dsh-preflight-'));
|
|
44
44
|
try {
|
|
45
45
|
const tar = join(dir, 'pkg.tar.gz');
|
|
46
46
|
await download(`https://codeload.github.com/${owner}/${repo}/tar.gz/${commit}`, tar);
|
|
47
47
|
const meta = await readTarJson(tar, 'package/package.json');
|
|
48
48
|
if (meta === null)
|
|
49
|
-
return { ok: true, missing: null };
|
|
49
|
+
return { ok: true, missing: null, name: null };
|
|
50
50
|
const dot = meta.exports?.['.'];
|
|
51
51
|
const resolved = typeof dot === 'string'
|
|
52
52
|
? dot
|
|
@@ -55,10 +55,10 @@ export async function preflightTarget(target) {
|
|
|
55
55
|
: undefined;
|
|
56
56
|
const entry = typeof resolved === 'string' ? resolved : typeof meta.main === 'string' ? meta.main : 'index.js';
|
|
57
57
|
const inTar = await hasTarEntry(tar, `package/${entry}`);
|
|
58
|
-
return { ok: inTar, missing: inTar ? null : entry };
|
|
58
|
+
return { ok: inTar, missing: inTar ? null : entry, name: typeof meta.name === 'string' ? meta.name : null };
|
|
59
59
|
}
|
|
60
60
|
catch {
|
|
61
|
-
return { ok: true, missing: null };
|
|
61
|
+
return { ok: true, missing: null, name: null };
|
|
62
62
|
}
|
|
63
63
|
finally {
|
|
64
64
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
/** 预检结果:ok=false 表示分发改入口文件缺失,missing
|
|
1
|
+
/** 预检结果:ok=false 表示分发改入口文件缺失,missing 为该文件在包内的相对路径;
|
|
2
|
+
* name 为 git 分发包 package.json 声明的包名(非 git 目标/无法确定时为 null),
|
|
3
|
+
* 供安装路由做「包名冲突」检测 —— 同名包名已被其他来源占用时,pnpm 装前必然撞车,
|
|
4
|
+
* 需要把晦涩的 CLI 报错转成明确的拦截。 */
|
|
2
5
|
export interface PreflightResult {
|
|
3
6
|
ok: boolean;
|
|
4
7
|
missing: string | null;
|
|
8
|
+
name: string | null;
|
|
5
9
|
}
|
|
6
10
|
export declare function preflightTarget(target: string): Promise<PreflightResult>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 4000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):4000+ 人工精选社区插件,每日更新。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { createElement as h, useEffect, useRef } from 'react'
|
|
12
12
|
import styles from '../../styles/List.module.css'
|
|
13
13
|
import type { HubPlugin, LocaleId, Translate } from '../../types.ts'
|
|
14
|
-
import {
|
|
14
|
+
import { siteUrl } from '../../logic/urls.ts'
|
|
15
15
|
import { useIncrementalList } from '../../hooks/useIncrementalList.ts'
|
|
16
16
|
import { PluginCard } from './PluginCard.tsx'
|
|
17
17
|
|
|
@@ -82,7 +82,7 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
|
|
|
82
82
|
plugins !== null && !failed && h('div', { className: styles.footer },
|
|
83
83
|
h('a', {
|
|
84
84
|
className: styles.footLink,
|
|
85
|
-
href:
|
|
85
|
+
href: siteUrl(langPath),
|
|
86
86
|
target: '_blank',
|
|
87
87
|
rel: 'noopener noreferrer',
|
|
88
88
|
}, t('browseAll', { n: total })),
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { createElement as h, Fragment } from 'react'
|
|
10
10
|
import styles from '../../styles/Header.module.css'
|
|
11
11
|
import type { Translate } from '../../types.ts'
|
|
12
|
-
import { GITHUB_URL, PLUGIN_VERSION
|
|
12
|
+
import { GITHUB_URL, PLUGIN_VERSION } from '../../logic/constants.ts'
|
|
13
|
+
import { siteUrl } from '../../logic/urls.ts'
|
|
13
14
|
import { GitHubIcon, LogoIcon } from '../ui/icons.tsx'
|
|
14
15
|
|
|
15
16
|
export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggleLang, hubUpdate, onVersionClick, onAboutClick }: {
|
|
@@ -31,7 +32,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
31
32
|
h('div', { className: styles.headerTitleRow },
|
|
32
33
|
h('a', {
|
|
33
34
|
className: styles.brandTitle,
|
|
34
|
-
href:
|
|
35
|
+
href: siteUrl(langPath),
|
|
35
36
|
target: '_blank',
|
|
36
37
|
rel: 'noopener noreferrer',
|
|
37
38
|
title: t('openHint'),
|
|
@@ -86,7 +87,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
86
87
|
// 第二行:副标题仍是官网链接,点击跳 dsh-plugin.org
|
|
87
88
|
h('a', {
|
|
88
89
|
className: styles.taglineLink,
|
|
89
|
-
href:
|
|
90
|
+
href: siteUrl(langPath),
|
|
90
91
|
target: '_blank',
|
|
91
92
|
rel: 'noopener noreferrer',
|
|
92
93
|
title: t('openHint'),
|
|
@@ -96,7 +97,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
|
|
|
96
97
|
),
|
|
97
98
|
h('a', {
|
|
98
99
|
className: styles.adBanner,
|
|
99
|
-
href:
|
|
100
|
+
href: siteUrl(langPath),
|
|
100
101
|
target: '_blank',
|
|
101
102
|
rel: 'noopener noreferrer',
|
|
102
103
|
},
|
package/src/client/logic/urls.ts
CHANGED
|
@@ -17,13 +17,19 @@ export function langPathOf(lang: LocaleId): string {
|
|
|
17
17
|
return lang === 'zh' ? 'zh/' : ''
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** 官网链接统一携带 `?ref=hub` 来源参数:统计有多少访问是从插件商城跳转而来。
|
|
21
|
+
* 所有「从商城跳官网」的链接(详情页/首页)都应经由此函数拼接,避免来源参数散落各处。 */
|
|
22
|
+
export function siteUrl(path: string): string {
|
|
23
|
+
return `${SITE_URL}${path}?ref=hub`
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* 官网详情页两级路径:/plugins/{ownerSlug}/{slug};
|
|
22
28
|
* 缺 ownerSlug 时从 repo 推导(卡片详情按钮与弹窗来源行共用)。
|
|
23
29
|
*/
|
|
24
30
|
export function pluginDetailUrl(plugin: HubPlugin, langPath: string): string {
|
|
25
31
|
const repo = plugin.source?.repo ?? ''
|
|
26
|
-
return `${
|
|
32
|
+
return siteUrl(`${langPath}plugins/${plugin.ownerSlug ?? repo.split('/')[0]?.toLowerCase() ?? ''}/${plugin.slug}`)
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
/**
|
|
@@ -31,7 +37,7 @@ export function pluginDetailUrl(plugin: HubPlugin, langPath: string): string {
|
|
|
31
37
|
* 与 issue 正文的 Catalog 链接同一形式(单级 /plugins/{repo})。
|
|
32
38
|
*/
|
|
33
39
|
export function pluginSiteUrl(repo: string): string {
|
|
34
|
-
return
|
|
40
|
+
return siteUrl(`plugins/${repo}`)
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
/** 按失败类型给出简洁的错误原因标题(对外用英文):
|
|
@@ -149,6 +149,22 @@ export function readInstalled(profile: string): Record<string, string> {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/** 读已装依赖实际落在磁盘上的仓库身份(`owner/repo`):解析 node_modules/<name>/package.json
|
|
153
|
+
* 的 repository 字段。已装 spec 可能是纯版本号(丢了仓库身份),但真实包元数据仍指向来源仓库,
|
|
154
|
+
* 据此可区分「同名依赖其实是同一仓库的残留(应转 update)」还是「跨仓库包名冲突(应报错)」。
|
|
155
|
+
* 读不到 / 解析不出返回 null(交给调用方按冲突处理)。 */
|
|
156
|
+
function installedRepoOf(profile: string, name: string): string | null {
|
|
157
|
+
try {
|
|
158
|
+
const pkg = JSON.parse(readFileSync(join(profileDirectory(profile), 'node_modules', name, 'package.json'), 'utf8')) as {
|
|
159
|
+
repository?: string | { url?: string }
|
|
160
|
+
}
|
|
161
|
+
const repo = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url
|
|
162
|
+
return repo ? githubRepoOf(repo) : null
|
|
163
|
+
} catch {
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
152
168
|
/** 宿主 dsh CLI 版本:优先直接跑 `dsh --version`(与 pnpm/npm/git 同款同步探测,1500ms 超时兜底,
|
|
153
169
|
* 绝不挂起)—— 原先从 process.argv[1] 向上找 package.json 猜版本在部分宿主加载方式下取不到,
|
|
154
170
|
* 导致 issue 环境快照 DSH: unknown;拿不到再退回入口 package.json 查找(dsh 不在 PATH 时兜底)。 */
|
|
@@ -649,8 +665,9 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
649
665
|
// 任务自动入队:即使已有插件操作在跑也接受请求(FIFO 串行执行),不再 409 拒绝
|
|
650
666
|
try {
|
|
651
667
|
const body = await readJsonBody(request)
|
|
652
|
-
// mode: 'update' = 已安装目标的覆盖更新(放行 add,pnpm
|
|
653
|
-
|
|
668
|
+
// mode: 'update' = 已安装目标的覆盖更新(放行 add,pnpm 对已存在依赖原位覆盖重装)。
|
|
669
|
+
// 后续「同仓库残留」检测可能把 install 就地升级为 update(见包名冲突检测),故用 let。
|
|
670
|
+
let mode = body !== null && typeof body === 'object' && (body as { mode?: unknown }).mode === 'update'
|
|
654
671
|
? 'update'
|
|
655
672
|
: 'install'
|
|
656
673
|
// installTargetOf 先把完整 DSH 命令(`dsh plugin [--profile <p>] add <target>`)剥成裸目标,
|
|
@@ -822,6 +839,31 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
|
|
|
822
839
|
})
|
|
823
840
|
return
|
|
824
841
|
}
|
|
842
|
+
// 包名冲突检测(仅 git 通道):git 目标包内声明的 name 若与 profile 已装依赖同名,
|
|
843
|
+
// pnpm 会以该 name 做依赖键撞车,抛出的 CLI 报错晦涩难懂(issue #25:sandbase-harness
|
|
844
|
+
// 声明 managed-agents,但该包名在 registry 上被另一仓库占用,npm 反查搜不到 → git 直装
|
|
845
|
+
// 撞已装同名依赖)。到这里已装依赖要么来源不同仓库、要么 spec 是版本号解析不出仓库身份。
|
|
846
|
+
// 读 node_modules 里已装包的真实 repository 区分两种情况:
|
|
847
|
+
// - 指向同一仓库 → 是「同仓库残留」(spec 丢了仓库身份),就地转 update 覆盖重装(幂等);
|
|
848
|
+
// - 指向别的仓库 / 读不到 → 跨仓库包名冲突,入队前转成明确 409,指导用户先卸载或改装。
|
|
849
|
+
if (preflight.name !== null && repoTarget !== null && target === repoTarget) {
|
|
850
|
+
const declared = preflight.name
|
|
851
|
+
if (installed[declared] !== undefined) {
|
|
852
|
+
const installedRepo = installedRepoOf(profile, declared)
|
|
853
|
+
if (installedRepo !== null && gitRepo !== null && installedRepo.toLowerCase() === gitRepo.toLowerCase()) {
|
|
854
|
+
// 同名且同源:已装 spec 丢了仓库身份导致的残留,直接按 update 覆盖重装,不打扰用户
|
|
855
|
+
attempts.push(`same-repo residual: \`${declared}\` resolves to \`${installedRepo}\` → reinstall as update`)
|
|
856
|
+
mode = 'update'
|
|
857
|
+
} else {
|
|
858
|
+
attempts.push(`package name conflict: \`${gitRepo}\` declares npm name \`${declared}\`, which is already present in the profile and cannot be matched to this repo (that npm name is likely registered by another repository)`)
|
|
859
|
+
const error = lang === 'zh'
|
|
860
|
+
? `already installed: ${declared} —— ${gitRepo} 声明的 npm 包名「${declared}」已存在于当前 profile,且无法匹配到本仓库(该包名在 npm 上可能被其他仓库占用)。请先移除现有的 ${declared}(dsh plugin remove ${declared}),再重新安装;或改装该包名对应的官方 npm 包。`
|
|
861
|
+
: `already installed: ${declared} — ${gitRepo} declares the npm package name "${declared}", which is already present in the profile and cannot be matched to this repo (that npm name is likely registered by another repository). Remove the existing ${declared} dependency (dsh plugin remove ${declared}) first, then retry; or install the official npm package under that name.`
|
|
862
|
+
sendJson(response, 409, { error, attempts })
|
|
863
|
+
return
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
825
867
|
}
|
|
826
868
|
// 入队前复检(与上方检查同语义,但必须在同步块内):preflight 的 await 期间并发请求
|
|
827
869
|
// 可能已通过上方检查、任务却尚未入队——此处检查与 startPluginMutation 之间无 await,
|
|
@@ -25,15 +25,19 @@ const execFileAsync = promisify(execFile)
|
|
|
25
25
|
* 否则 `/install` 请求永远不返回、任务不入队、前端进度卡 0%。超时后放行(交给装后校验)。 */
|
|
26
26
|
const PREFLIGHT_TIMEOUT_MS = 15_000
|
|
27
27
|
|
|
28
|
-
/** 预检结果:ok=false 表示分发改入口文件缺失,missing
|
|
28
|
+
/** 预检结果:ok=false 表示分发改入口文件缺失,missing 为该文件在包内的相对路径;
|
|
29
|
+
* name 为 git 分发包 package.json 声明的包名(非 git 目标/无法确定时为 null),
|
|
30
|
+
* 供安装路由做「包名冲突」检测 —— 同名包名已被其他来源占用时,pnpm 装前必然撞车,
|
|
31
|
+
* 需要把晦涩的 CLI 报错转成明确的拦截。 */
|
|
29
32
|
export interface PreflightResult {
|
|
30
33
|
ok: boolean
|
|
31
34
|
missing: string | null
|
|
35
|
+
name: string | null
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
export async function preflightTarget(target: string): Promise<PreflightResult> {
|
|
35
39
|
const source = githubRepoOf(target)
|
|
36
|
-
if (source === null) return { ok: true, missing: null }
|
|
40
|
+
if (source === null) return { ok: true, missing: null, name: null }
|
|
37
41
|
const [owner, repo] = source.split('/')
|
|
38
42
|
let commit = ''
|
|
39
43
|
try {
|
|
@@ -43,15 +47,15 @@ export async function preflightTarget(target: string): Promise<PreflightResult>
|
|
|
43
47
|
})
|
|
44
48
|
commit = stdout.split(/\s+/)[0] ?? ''
|
|
45
49
|
} catch {
|
|
46
|
-
return { ok: true, missing: null }
|
|
50
|
+
return { ok: true, missing: null, name: null }
|
|
47
51
|
}
|
|
48
|
-
if (!commit) return { ok: true, missing: null }
|
|
52
|
+
if (!commit) return { ok: true, missing: null, name: null }
|
|
49
53
|
const dir = mkdtempSync(join(tmpdir(), 'dsh-preflight-'))
|
|
50
54
|
try {
|
|
51
55
|
const tar = join(dir, 'pkg.tar.gz')
|
|
52
56
|
await download(`https://codeload.github.com/${owner}/${repo}/tar.gz/${commit}`, tar)
|
|
53
57
|
const meta = await readTarJson(tar, 'package/package.json')
|
|
54
|
-
if (meta === null) return { ok: true, missing: null }
|
|
58
|
+
if (meta === null) return { ok: true, missing: null, name: null }
|
|
55
59
|
const dot = (meta.exports as Record<string, unknown> | undefined)?.['.']
|
|
56
60
|
const resolved = typeof dot === 'string'
|
|
57
61
|
? dot
|
|
@@ -60,9 +64,9 @@ export async function preflightTarget(target: string): Promise<PreflightResult>
|
|
|
60
64
|
: undefined
|
|
61
65
|
const entry = typeof resolved === 'string' ? resolved : typeof meta.main === 'string' ? meta.main : 'index.js'
|
|
62
66
|
const inTar = await hasTarEntry(tar, `package/${entry}`)
|
|
63
|
-
return { ok: inTar, missing: inTar ? null : entry }
|
|
67
|
+
return { ok: inTar, missing: inTar ? null : entry, name: typeof meta.name === 'string' ? meta.name : null }
|
|
64
68
|
} catch {
|
|
65
|
-
return { ok: true, missing: null }
|
|
69
|
+
return { ok: true, missing: null, name: null }
|
|
66
70
|
} finally {
|
|
67
71
|
rmSync(dir, { recursive: true, force: true })
|
|
68
72
|
}
|