@ticatec/omniflow-nexus 1.0.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.
@@ -0,0 +1,217 @@
1
+ import { promises as fs } from 'fs'
2
+ import path from 'path'
3
+ import crypto from 'crypto'
4
+ import { BasePublisher } from '@ticatec/omniflow-artifact'
5
+ import type { ArtifactPayload, DownloadOptions, DownloadResult, PublishOptions, RegistryConfig } from '@ticatec/omniflow-artifact'
6
+ import { NexusClient } from '../client/NexusClient.js'
7
+
8
+ /**
9
+ * Sonatype Nexus 3 Raw (Generic) Package Repository Publisher.
10
+ *
11
+ * Publishes arbitrary binary artifacts, tarballs (.tar.gz), or zip files
12
+ * to Nexus "raw (hosted)" repositories.
13
+ * Endpoint format: PUT {server}/repository/{repoName}/{path}/{filename}
14
+ */
15
+ export class NexusRawPublisher extends BasePublisher {
16
+ readonly id = 'nexus-raw'
17
+ readonly name = 'Nexus 3 Raw Repository'
18
+ readonly platform = 'nexus'
19
+ readonly artifactType = 'generic'
20
+ override readonly priority = 20
21
+
22
+ override detect(registry: RegistryConfig): boolean {
23
+ if (registry.type !== 'generic' && registry.type !== 'raw' && registry.type !== 'archive') {
24
+ return false
25
+ }
26
+
27
+ const regName = (registry.name || '').toLowerCase()
28
+ if (regName === 'nexus' || regName.startsWith('nexus-') || regName === 'nexus-raw') {
29
+ return true
30
+ }
31
+
32
+ const server = (registry.server || '').toLowerCase()
33
+ try {
34
+ const parsed = new URL(server.includes('://') ? server : `https://${server}`)
35
+ const host = parsed.hostname.toLowerCase()
36
+
37
+ // Exclude other known platforms
38
+ if (
39
+ host === 'aliyun.com' ||
40
+ host.endsWith('.aliyun.com') ||
41
+ host.endsWith('.aliyuncs.com') ||
42
+ host === 'gitlab.com' ||
43
+ host.endsWith('.gitlab.com') ||
44
+ host === 'github.com' ||
45
+ host.endsWith('.github.com')
46
+ ) {
47
+ return false
48
+ }
49
+
50
+ return host.includes('nexus') || parsed.pathname.includes('/repository/raw')
51
+ } catch {
52
+ return false
53
+ }
54
+ }
55
+
56
+ override buildTargetRef(artifact: ArtifactPayload, registry: RegistryConfig): string {
57
+ const repoName =
58
+ registry.namespace?.trim() ||
59
+ (registry.options?.repository as string | undefined)?.trim() ||
60
+ ((registry as any).metadata?.repository as string | undefined)?.trim() ||
61
+ 'raw-hosted'
62
+ const version = artifact.tags && artifact.tags.length > 0 ? artifact.tags[0] : 'latest'
63
+ const filename = artifact.source ? path.basename(artifact.source) : artifact.name
64
+ return `${repoName}/${artifact.name}/${version}/${filename}`
65
+ }
66
+
67
+ async login(registry: RegistryConfig, _options?: PublishOptions): Promise<void> {
68
+ const creds = this.resolveCredentials(registry)
69
+ if (!creds.token && (!creds.username || !creds.password)) {
70
+ this.log(` ℹ No auth credentials configured for Nexus Raw repository ${registry.server}`)
71
+ }
72
+ }
73
+
74
+ override async exists(
75
+ artifact: ArtifactPayload,
76
+ registry: RegistryConfig,
77
+ options?: PublishOptions
78
+ ): Promise<boolean> {
79
+ const creds = this.resolveCredentials(registry)
80
+ const client = new NexusClient({
81
+ baseUrl: registry.server,
82
+ username: creds.username,
83
+ password: creds.password,
84
+ token: creds.token,
85
+ timeoutMs: options?.timeoutMs
86
+ })
87
+
88
+ const repoName = registry.namespace || 'raw-hosted'
89
+ const version = artifact.tags && artifact.tags.length > 0 ? artifact.tags[0] : 'latest'
90
+ const packageName = artifact.name
91
+
92
+ const filename = artifact.source ? path.basename(artifact.source) : artifact.name
93
+ const subpath = `${packageName}/${version}`
94
+ const targetUrl = client.getRawArtifactUrl(repoName, subpath, filename)
95
+
96
+ try {
97
+ const res = await client.request(targetUrl, { method: 'HEAD' })
98
+ if (res.ok || res.status === 200 || res.status === 204) {
99
+ return true
100
+ }
101
+ if (res.status === 404) {
102
+ return false
103
+ }
104
+ const err = await client.extractErrorMessage(res)
105
+ throw new Error(`Failed to check Raw package existence (${res.status}): ${err}`)
106
+ } catch (err: any) {
107
+ if (err?.message?.includes('Failed to check Raw package existence')) {
108
+ throw err
109
+ }
110
+ throw new Error(`Failed to check Raw package existence: ${client.maskSecrets(err?.message || String(err))}`)
111
+ }
112
+ }
113
+
114
+ override async download(
115
+ artifact: ArtifactPayload,
116
+ registry: RegistryConfig,
117
+ dest: string,
118
+ options?: DownloadOptions
119
+ ): Promise<DownloadResult> {
120
+ const creds = this.resolveCredentials(registry)
121
+ const client = new NexusClient({
122
+ baseUrl: registry.server,
123
+ username: creds.username,
124
+ password: creds.password,
125
+ token: creds.token,
126
+ timeoutMs: options?.timeoutMs
127
+ })
128
+
129
+ const repoName = registry.namespace || 'raw-hosted'
130
+ const version = artifact.tags?.[0] || 'latest'
131
+ const filename = artifact.source ? path.basename(artifact.source) : artifact.name
132
+ const targetUrl = client.getRawArtifactUrl(repoName, `${artifact.name}/${version}`, filename)
133
+ const masked = client.maskSecrets(targetUrl)
134
+
135
+ this.log(` 📥 Downloading ${artifact.name} ${version} from Nexus: ${masked}`)
136
+ const res = await client.request(targetUrl, { method: 'GET' })
137
+ if (!res.ok) {
138
+ throw await this.downloadError(res, `${artifact.name} ${version} (${filename})`, masked)
139
+ }
140
+ return this.saveResponse(res, dest, masked)
141
+ }
142
+
143
+ protected async doPublish(
144
+ artifact: ArtifactPayload,
145
+ registry: RegistryConfig,
146
+ options?: PublishOptions
147
+ ): Promise<{
148
+ ref: string
149
+ digest?: string
150
+ metadata?: Record<string, unknown>
151
+ }> {
152
+ const creds = this.resolveCredentials(registry)
153
+ const client = new NexusClient({
154
+ baseUrl: registry.server,
155
+ username: creds.username,
156
+ password: creds.password,
157
+ token: creds.token,
158
+ timeoutMs: options?.timeoutMs
159
+ })
160
+
161
+ const repoName = registry.namespace || 'raw-hosted'
162
+ const version = artifact.tags && artifact.tags.length > 0 ? artifact.tags[0] : 'latest'
163
+ const packageName = artifact.name
164
+
165
+ let filePath = artifact.source
166
+ const stat = await fs.stat(filePath).catch(() => null)
167
+ if (!stat || !stat.isFile()) {
168
+ throw new Error(`Nexus Raw artifact source must be an existing file: ${filePath}`)
169
+ }
170
+
171
+ const filename = path.basename(filePath)
172
+ const fileBuffer = await fs.readFile(filePath)
173
+ const digest = `sha256:${crypto.createHash('sha256').update(fileBuffer).digest('hex')}`
174
+
175
+ const subpath = `${packageName}/${version}`
176
+ const targetUrl = client.getRawArtifactUrl(repoName, subpath, filename)
177
+ this.log(` 📤 Uploading Raw package file to Nexus: ${client.maskSecrets(targetUrl)}`)
178
+
179
+ if (options?.dryRun) {
180
+ this.log(` [dryRun] Would PUT ${filename} (${fileBuffer.length} bytes) to ${client.maskSecrets(targetUrl)}`)
181
+ return {
182
+ ref: `${repoName}/${subpath}/${filename}`,
183
+ digest,
184
+ metadata: { repoName, subpath, filename, targetUrl, size: fileBuffer.length }
185
+ }
186
+ }
187
+
188
+ const res = await client.request(targetUrl, {
189
+ method: 'PUT',
190
+ headers: {
191
+ 'Content-Type': 'application/octet-stream'
192
+ },
193
+ body: fileBuffer as unknown as BodyInit
194
+ })
195
+
196
+ if (!res.ok && res.status !== 201 && res.status !== 200) {
197
+ const errText = await client.extractErrorMessage(res)
198
+ throw new Error(`Failed to upload Raw package to Nexus (${res.status}): ${errText}`)
199
+ }
200
+
201
+ return {
202
+ ref: `${repoName}/${subpath}/${filename}`,
203
+ digest,
204
+ metadata: {
205
+ repoName,
206
+ packageName,
207
+ version,
208
+ filename,
209
+ targetUrl,
210
+ size: fileBuffer.length
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ // Convenient alias
217
+ export { NexusRawPublisher as NexusGenericPublisher }