dsh-enterprise-market 0.1.0 → 0.2.0
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/client.js +10 -8
- package/package.json +1 -1
- package/src/host/npm.js +58 -4
package/lib/client.js
CHANGED
|
@@ -31,8 +31,8 @@ window.__ModuleLoader__.load({
|
|
|
31
31
|
updated: '更新',
|
|
32
32
|
local: '本机发布',
|
|
33
33
|
uploadHeading: '发布自建插件',
|
|
34
|
-
uploadHint: '
|
|
35
|
-
chooseFile: '选择 .tgz 文件',
|
|
34
|
+
uploadHint: '选择 .zip 或 .tgz,发布到企业 npm 仓库。zip 由宿主自动解包打包,上传人姓名取自当前企业身份并写入包元数据,同事在市场里就能看到。',
|
|
35
|
+
chooseFile: '选择 .zip 或 .tgz 文件',
|
|
36
36
|
noFile: '尚未选择文件',
|
|
37
37
|
namePlaceholder: '包名(可留空,用包内 package.json)',
|
|
38
38
|
descPlaceholder: '一句话用途(可留空)',
|
|
@@ -41,8 +41,8 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
published: '已发布',
|
|
42
42
|
publishFailed: '发布失败',
|
|
43
43
|
needIdentity: '未认证企业身份,不能发布',
|
|
44
|
-
needFile: '请先选择 .tgz 文件',
|
|
45
|
-
packHint: '
|
|
44
|
+
needFile: '请先选择 .zip 或 .tgz 文件',
|
|
45
|
+
packHint: '直接上传插件目录压缩成的 .zip,或已打包的 .tgz;插件名必须以 traness- 开头。',
|
|
46
46
|
npmSignedIn: 'npm 已登录',
|
|
47
47
|
npmSignedOut: 'npm 未登录',
|
|
48
48
|
npmLoginHint: '本机 npm 未登录,发布会被仓库拒绝。先在终端执行 npm login,再回来上传。',
|
|
@@ -56,7 +56,9 @@ window.__ModuleLoader__.load({
|
|
|
56
56
|
'origin-refused': '请求来源不受信任,已拒绝。',
|
|
57
57
|
'registry-unreachable': '企业仓库暂时不可达。',
|
|
58
58
|
'publish-failed': 'npm 拒绝发布,请检查包名是否已被占用、是否已登录。',
|
|
59
|
-
'invalid-tarball': '
|
|
59
|
+
'invalid-tarball': '这个归档读不出 package.json,请确认压缩包里是插件目录本身。',
|
|
60
|
+
'invalid-archive': '压缩包里找不到 package.json,请把插件目录(含 package.json)打成一个 zip。',
|
|
61
|
+
'name-prefix-required': '插件名必须以 traness- 开头,请修改 package.json 的 name 后重新上传。',
|
|
60
62
|
'invalid-manifest': '包内 package.json 缺少 name 或 version。',
|
|
61
63
|
'identity-unverified': '未认证企业身份,不能发布。',
|
|
62
64
|
'credential-missing': '没有可用的企业令牌。',
|
|
@@ -94,8 +96,8 @@ window.__ModuleLoader__.load({
|
|
|
94
96
|
updated: 'Updated',
|
|
95
97
|
local: 'Published locally',
|
|
96
98
|
uploadHeading: 'Publish a self-built plugin',
|
|
97
|
-
uploadHint: 'Pick
|
|
98
|
-
chooseFile: 'Choose a .tgz file',
|
|
99
|
+
uploadHint: 'Pick a .zip or .tgz and publish it to the enterprise npm registry. A zip is unpacked and packed by the host; your verified name is written into the package metadata so colleagues see the uploader.',
|
|
100
|
+
chooseFile: 'Choose a .zip or .tgz file',
|
|
99
101
|
noFile: 'No file selected',
|
|
100
102
|
namePlaceholder: 'Package name (optional; defaults to the package.json inside)',
|
|
101
103
|
descPlaceholder: 'One-line purpose (optional)',
|
|
@@ -552,7 +554,7 @@ window.__ModuleLoader__.load({
|
|
|
552
554
|
h('input', {
|
|
553
555
|
ref: inputRef,
|
|
554
556
|
type: 'file',
|
|
555
|
-
accept: '.tgz,.tar.gz,application/gzip',
|
|
557
|
+
accept: '.zip,.tgz,.tar.gz,application/zip,application/gzip',
|
|
556
558
|
className: 'emk-hidden',
|
|
557
559
|
onChange: (event) => {
|
|
558
560
|
setFile(event.target.files?.[0] ?? null)
|
package/package.json
CHANGED
package/src/host/npm.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
1
|
+
import { access, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { createHash } from 'node:crypto'
|
|
5
|
+
|
|
6
|
+
const exists = async (path) => {
|
|
7
|
+
try {
|
|
8
|
+
await access(path)
|
|
9
|
+
return true
|
|
10
|
+
} catch {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
5
14
|
import { findCli, run } from './exec.js'
|
|
6
15
|
|
|
7
16
|
/** The metadata field the market reads the uploader from. */
|
|
@@ -51,6 +60,18 @@ export function isTarballName(fileName) {
|
|
|
51
60
|
return /\.(tgz|tar\.gz)$/i.test(fileName)
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
/** Whether the spec names an archive this market can publish: tgz or zip. */
|
|
64
|
+
export function isArchiveName(fileName) {
|
|
65
|
+
return isTarballName(fileName) || /[.]zip$/i.test(fileName)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 企业插件命名硬约束:必须以 traness- 开头,不做自动改名。 */
|
|
69
|
+
export const PLUGIN_NAME_PREFIX = 'traness-'
|
|
70
|
+
|
|
71
|
+
/** @param {string} name */
|
|
72
|
+
export function nameMatchesPolicy(name) {
|
|
73
|
+
return typeof name === 'string' && name.startsWith(PLUGIN_NAME_PREFIX) && name.length > PLUGIN_NAME_PREFIX.length
|
|
74
|
+
}
|
|
54
75
|
/**
|
|
55
76
|
* Extract a tarball, inject the uploader metadata and the discovery keyword,
|
|
56
77
|
* and publish the resulting directory with the user's npm. The directory is a
|
|
@@ -69,6 +90,25 @@ export function isTarballName(fileName) {
|
|
|
69
90
|
* run?: typeof run,
|
|
70
91
|
* }} input
|
|
71
92
|
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 找到插件根目录:npm tarball 固定在 'package/';zip 允许根目录直接是插件,
|
|
96
|
+
* 或只包一层文件夹(两种结构都支持)。
|
|
97
|
+
*
|
|
98
|
+
* @param {string} scratch
|
|
99
|
+
* @param {boolean} isZip
|
|
100
|
+
*/
|
|
101
|
+
async function locatePluginRoot(scratch, isZip) {
|
|
102
|
+
const direct = join(scratch, isZip ? '' : 'package', 'package.json')
|
|
103
|
+
if (await exists(direct)) return join(scratch, isZip ? '' : 'package')
|
|
104
|
+
if (!isZip) return null
|
|
105
|
+
const entries = await readdir(scratch, { withFileTypes: true })
|
|
106
|
+
const dirs = entries.filter((entry) => entry.isDirectory())
|
|
107
|
+
if (dirs.length !== 1) return null
|
|
108
|
+
const nested = join(scratch, dirs[0].name)
|
|
109
|
+
if (!(await exists(join(nested, 'package.json')))) return null
|
|
110
|
+
return nested
|
|
111
|
+
}
|
|
72
112
|
export async function publishTarball(input) {
|
|
73
113
|
const registry = registryBase(input.registry)
|
|
74
114
|
const npm = findCli(input.npmBin ?? 'npm')
|
|
@@ -79,14 +119,19 @@ export async function publishTarball(input) {
|
|
|
79
119
|
let authFile
|
|
80
120
|
try {
|
|
81
121
|
await writeFile(tarballPath, input.tarball, { mode: 0o600 })
|
|
82
|
-
const
|
|
122
|
+
const isZip = /[.]zip$/i.test(input.fileName)
|
|
123
|
+
const extracted = await (input.run ?? run)('tar', [isZip ? '-xf' : '-xzf', tarballPath, '-C', scratch], {
|
|
83
124
|
timeoutMs: 30_000,
|
|
84
125
|
})
|
|
85
126
|
if (extracted.code !== 0) {
|
|
86
127
|
return { ok: false, reason: 'invalid-tarball', detail: 'tar could not read the uploaded file' }
|
|
87
128
|
}
|
|
88
|
-
// A packed npm tarball puts everything under one
|
|
89
|
-
|
|
129
|
+
// A packed npm tarball puts everything under one 'package/' directory; a
|
|
130
|
+
// user zip may put package.json at the root or inside a single top folder.
|
|
131
|
+
const pkgDir = await locatePluginRoot(scratch, isZip)
|
|
132
|
+
if (pkgDir === null) {
|
|
133
|
+
return { ok: false, reason: 'invalid-archive', detail: 'the archive has no package.json at its root' }
|
|
134
|
+
}
|
|
90
135
|
let manifest
|
|
91
136
|
try {
|
|
92
137
|
manifest = JSON.parse(await readFile(join(pkgDir, 'package.json'), 'utf8'))
|
|
@@ -99,6 +144,15 @@ export async function publishTarball(input) {
|
|
|
99
144
|
if (typeof manifest.version !== 'string' || manifest.version.trim() === '') {
|
|
100
145
|
return { ok: false, reason: 'invalid-manifest', detail: 'package.json has no version' }
|
|
101
146
|
}
|
|
147
|
+
const requestedName = typeof input.name === 'string' ? input.name.trim() : ''
|
|
148
|
+
const effectiveName = requestedName !== '' ? requestedName : manifest.name
|
|
149
|
+
if (!nameMatchesPolicy(effectiveName)) {
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
reason: 'name-prefix-required',
|
|
153
|
+
detail: '插件名必须以 ' + PLUGIN_NAME_PREFIX + ' 开头,当前为 ' + effectiveName,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
102
156
|
const name = typeof input.name === 'string' && input.name.trim() !== '' ? input.name.trim() : manifest.name
|
|
103
157
|
const description =
|
|
104
158
|
typeof input.description === 'string' && input.description.trim() !== ''
|