@ticatec/omniflow-github 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.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/README_CN.md +93 -0
- package/dist/artifacts/GitHubContainerPublisher.d.ts +16 -0
- package/dist/artifacts/GitHubContainerPublisher.d.ts.map +1 -0
- package/dist/artifacts/GitHubContainerPublisher.js +47 -0
- package/dist/artifacts/GitHubContainerPublisher.js.map +1 -0
- package/dist/artifacts/GitHubMavenPublisher.d.ts +25 -0
- package/dist/artifacts/GitHubMavenPublisher.d.ts.map +1 -0
- package/dist/artifacts/GitHubMavenPublisher.js +186 -0
- package/dist/artifacts/GitHubMavenPublisher.js.map +1 -0
- package/dist/artifacts/GitHubNpmPublisher.d.ts +21 -0
- package/dist/artifacts/GitHubNpmPublisher.d.ts.map +1 -0
- package/dist/artifacts/GitHubNpmPublisher.js +49 -0
- package/dist/artifacts/GitHubNpmPublisher.js.map +1 -0
- package/dist/artifacts/GitHubReleasePublisher.d.ts +31 -0
- package/dist/artifacts/GitHubReleasePublisher.d.ts.map +1 -0
- package/dist/artifacts/GitHubReleasePublisher.js +192 -0
- package/dist/artifacts/GitHubReleasePublisher.js.map +1 -0
- package/dist/client/GitHubClient.d.ts +76 -0
- package/dist/client/GitHubClient.d.ts.map +1 -0
- package/dist/client/GitHubClient.js +170 -0
- package/dist/client/GitHubClient.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/src/artifacts/GitHubContainerPublisher.ts +54 -0
- package/src/artifacts/GitHubMavenPublisher.ts +238 -0
- package/src/artifacts/GitHubNpmPublisher.ts +66 -0
- package/src/artifacts/GitHubReleasePublisher.ts +238 -0
- package/src/client/GitHubClient.ts +242 -0
- package/src/index.ts +54 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { promises as fs } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import crypto from 'crypto'
|
|
4
|
+
import { BasePublisher, resolveMavenArtifactFilenames, mavenPomPathFor, isMavenArtifactFile } from '@ticatec/omniflow-artifact'
|
|
5
|
+
import type { ArtifactPayload, PublishOptions, RegistryConfig } from '@ticatec/omniflow-artifact'
|
|
6
|
+
import { GitHubClient } from '../client/GitHubClient.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GitHub Maven Package Registry Publisher.
|
|
10
|
+
*
|
|
11
|
+
* Endpoint format: PUT https://maven.pkg.github.com/<owner>/<repo>/{groupId}/{artifactId}/{version}/{filename}
|
|
12
|
+
*/
|
|
13
|
+
export class GitHubMavenPublisher extends BasePublisher {
|
|
14
|
+
readonly id = 'github-maven'
|
|
15
|
+
readonly name = 'GitHub Packages (Maven)'
|
|
16
|
+
readonly platform = 'github'
|
|
17
|
+
readonly artifactType = 'maven'
|
|
18
|
+
override readonly priority = 20
|
|
19
|
+
|
|
20
|
+
override detect(registry: RegistryConfig): boolean {
|
|
21
|
+
if (registry.type !== 'maven' && registry.type !== 'jar') {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const regName = (registry.name || '').toLowerCase()
|
|
26
|
+
if (regName === 'github' || regName.startsWith('github-')) {
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const server = (registry.server || '').toLowerCase()
|
|
31
|
+
try {
|
|
32
|
+
const parsed = new URL(server.includes('://') ? server : `https://${server}`)
|
|
33
|
+
return parsed.hostname === 'maven.pkg.github.com' || GitHubClient.isGitHubHost(parsed.hostname)
|
|
34
|
+
} catch {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override buildTargetRef(artifact: ArtifactPayload, _registry: RegistryConfig): string {
|
|
40
|
+
const version = artifact.tags && artifact.tags.length > 0 ? artifact.tags[0] : '1.0.0'
|
|
41
|
+
const artifactId = (artifact.metadata?.artifactId as string) || artifact.name
|
|
42
|
+
const groupId = (artifact.metadata?.groupId as string)?.trim()
|
|
43
|
+
return groupId ? `${groupId}:${artifactId}:${version}` : `${artifactId}:${version}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected resolveBaseRepoUrl(registry: RegistryConfig): string {
|
|
47
|
+
let repoPath = registry.namespace || ''
|
|
48
|
+
let server = registry.server || 'https://maven.pkg.github.com'
|
|
49
|
+
if (!/^https?:\/\//i.test(server)) {
|
|
50
|
+
server = `https://${server}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (server.includes('maven.pkg.github.com') && server.split('/').filter(Boolean).length >= 4) {
|
|
54
|
+
return server.replace(/\/+$/, '')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!repoPath) {
|
|
58
|
+
throw new Error(`GitHub Maven packages require repository namespace (<owner>/<repo>) to be configured.`)
|
|
59
|
+
}
|
|
60
|
+
return `https://maven.pkg.github.com/${repoPath.replace(/^\/+/, '').replace(/\/+$/, '')}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override async exists(
|
|
64
|
+
artifact: ArtifactPayload,
|
|
65
|
+
registry: RegistryConfig,
|
|
66
|
+
options?: PublishOptions
|
|
67
|
+
): Promise<boolean> {
|
|
68
|
+
const creds = this.resolveCredentials(registry)
|
|
69
|
+
const token = creds.token || creds.password
|
|
70
|
+
|
|
71
|
+
let baseRepoUrl: string
|
|
72
|
+
try {
|
|
73
|
+
baseRepoUrl = this.resolveBaseRepoUrl(registry)
|
|
74
|
+
} catch {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const client = new GitHubClient({
|
|
79
|
+
token,
|
|
80
|
+
timeoutMs: options?.timeoutMs
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
let jarPath = artifact.source
|
|
84
|
+
if (jarPath) {
|
|
85
|
+
const stat = await fs.stat(jarPath).catch(() => null)
|
|
86
|
+
if (stat?.isDirectory()) {
|
|
87
|
+
const files = await fs.readdir(jarPath).catch(() => [] as string[])
|
|
88
|
+
const jarFile = files.find(
|
|
89
|
+
f => isMavenArtifactFile(f)
|
|
90
|
+
)
|
|
91
|
+
if (jarFile) {
|
|
92
|
+
jarPath = path.join(jarPath, jarFile)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { groupId, artifactId, version, groupPath, jarFilename } = resolveMavenArtifactFilenames(
|
|
98
|
+
artifact,
|
|
99
|
+
jarPath,
|
|
100
|
+
undefined,
|
|
101
|
+
'GitHub'
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const probeUrl = `${baseRepoUrl}/${groupPath}/${artifactId}/${version}/${jarFilename}`
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const res = await client.request(probeUrl, { method: 'HEAD' })
|
|
108
|
+
if (res.ok || res.status === 200 || res.status === 204) {
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
if (res.status === 404) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
const errText = await client.extractErrorMessage(res)
|
|
115
|
+
throw new Error(`Failed to check artifact existence in GitHub Maven registry (${res.status}): ${errText}`)
|
|
116
|
+
} catch (err: any) {
|
|
117
|
+
if (err?.message?.includes('Failed to check artifact existence in GitHub')) {
|
|
118
|
+
throw err
|
|
119
|
+
}
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Failed to check artifact existence in GitHub Maven registry: ${client.maskSecrets(err?.message || String(err))}`
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async login(registry: RegistryConfig, _options?: PublishOptions): Promise<void> {
|
|
127
|
+
const creds = this.resolveCredentials(registry)
|
|
128
|
+
if (!creds.token && !creds.password) {
|
|
129
|
+
this.log(` ℹ No auth token configured for GitHub Maven repository ${registry.server}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected async doPublish(
|
|
134
|
+
artifact: ArtifactPayload,
|
|
135
|
+
registry: RegistryConfig,
|
|
136
|
+
options?: PublishOptions
|
|
137
|
+
): Promise<{
|
|
138
|
+
ref: string
|
|
139
|
+
digest?: string
|
|
140
|
+
metadata?: Record<string, unknown>
|
|
141
|
+
}> {
|
|
142
|
+
const creds = this.resolveCredentials(registry)
|
|
143
|
+
const token = creds.token || creds.password
|
|
144
|
+
const repoBaseUrl = this.resolveBaseRepoUrl(registry)
|
|
145
|
+
|
|
146
|
+
const client = new GitHubClient({
|
|
147
|
+
token,
|
|
148
|
+
timeoutMs: options?.timeoutMs
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Locate jar file
|
|
152
|
+
let jarPath = artifact.source
|
|
153
|
+
const stat = await fs.stat(jarPath).catch(() => null)
|
|
154
|
+
if (stat?.isDirectory()) {
|
|
155
|
+
const files = await fs.readdir(jarPath)
|
|
156
|
+
const jarFile = files.find(f => isMavenArtifactFile(f))
|
|
157
|
+
if (!jarFile) {
|
|
158
|
+
throw new Error(`No jar/war artifact found in directory: ${jarPath}`)
|
|
159
|
+
}
|
|
160
|
+
jarPath = path.join(jarPath, jarFile)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { groupId, artifactId, version, groupPath, jarFilename, pomFilename } = resolveMavenArtifactFilenames(
|
|
164
|
+
artifact,
|
|
165
|
+
jarPath,
|
|
166
|
+
undefined,
|
|
167
|
+
'GitHub'
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
const jarBuffer = await fs.readFile(jarPath)
|
|
171
|
+
const digest = `sha256:${crypto.createHash('sha256').update(jarBuffer).digest('hex')}`
|
|
172
|
+
|
|
173
|
+
const targetUrl = `${repoBaseUrl}/${groupPath}/${artifactId}/${version}/${jarFilename}`
|
|
174
|
+
this.log(` 📤 Uploading Maven JAR to GitHub: ${targetUrl}`)
|
|
175
|
+
|
|
176
|
+
if (options?.dryRun) {
|
|
177
|
+
this.log(` [dryRun] Would PUT ${jarFilename} (${jarBuffer.length} bytes) to ${targetUrl}`)
|
|
178
|
+
return {
|
|
179
|
+
ref: `${groupId}:${artifactId}:${version}`,
|
|
180
|
+
digest,
|
|
181
|
+
metadata: { targetUrl, size: jarBuffer.length }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The POM goes up first, deliberately.
|
|
186
|
+
//
|
|
187
|
+
// The existence probe checks for the jar/war, so uploading that last is what
|
|
188
|
+
// makes the probe mean "this artifact is complete". With the artifact first, a
|
|
189
|
+
// run that uploaded it and then failed on the POM would leave a coordinate that
|
|
190
|
+
// probes as present: the retry skips it as already published, and the POM never
|
|
191
|
+
// arrives — a jar in the repository forever, with no dependency information.
|
|
192
|
+
// Upload pom if exists
|
|
193
|
+
const pomPath = mavenPomPathFor(jarPath)
|
|
194
|
+
const pomStat = await fs.stat(pomPath).catch(() => null)
|
|
195
|
+
if (pomStat?.isFile()) {
|
|
196
|
+
const pomBuffer = await fs.readFile(pomPath)
|
|
197
|
+
const targetPomFilename = pomFilename || `${artifactId}-${version}.pom`
|
|
198
|
+
const pomTargetUrl = `${repoBaseUrl}/${groupPath}/${artifactId}/${version}/${targetPomFilename}`
|
|
199
|
+
this.log(` 📤 Uploading Maven POM to GitHub: ${pomTargetUrl}`)
|
|
200
|
+
|
|
201
|
+
const pomRes = await client.request(pomTargetUrl, {
|
|
202
|
+
method: 'PUT',
|
|
203
|
+
headers: { 'Content-Type': 'application/xml' },
|
|
204
|
+
body: pomBuffer
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
if (!pomRes.ok && pomRes.status !== 201 && pomRes.status !== 200) {
|
|
208
|
+
const pomErr = await client.extractErrorMessage(pomRes)
|
|
209
|
+
throw new Error(`Failed to upload Maven POM to GitHub (${pomRes.status}): ${pomErr}`)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const res = await client.request(targetUrl, {
|
|
214
|
+
method: 'PUT',
|
|
215
|
+
headers: {
|
|
216
|
+
'Content-Type': 'application/java-archive'
|
|
217
|
+
},
|
|
218
|
+
body: jarBuffer
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
if (!res.ok && res.status !== 201 && res.status !== 200) {
|
|
222
|
+
const errText = await client.extractErrorMessage(res)
|
|
223
|
+
throw new Error(`Failed to upload Maven JAR to GitHub (${res.status}): ${errText}`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
ref: `${groupId}:${artifactId}:${version}`,
|
|
228
|
+
digest,
|
|
229
|
+
metadata: {
|
|
230
|
+
groupId,
|
|
231
|
+
artifactId,
|
|
232
|
+
version,
|
|
233
|
+
targetUrl,
|
|
234
|
+
size: jarBuffer.length
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BaseNpmPublisher } from '@ticatec/omniflow-artifact'
|
|
2
|
+
import type { ArtifactPayload, PublishOptions, RegistryConfig } from '@ticatec/omniflow-artifact'
|
|
3
|
+
import { GitHubClient } from '../client/GitHubClient.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GitHub NPM Package Registry Publisher.
|
|
7
|
+
*
|
|
8
|
+
* Endpoint format: https://npm.pkg.github.com/
|
|
9
|
+
* GitHub requires npm packages to be scoped, e.g. @owner/package-name.
|
|
10
|
+
*/
|
|
11
|
+
export class GitHubNpmPublisher extends BaseNpmPublisher {
|
|
12
|
+
readonly id = 'github-npm'
|
|
13
|
+
readonly name = 'GitHub Packages (NPM)'
|
|
14
|
+
readonly platform = 'github'
|
|
15
|
+
override readonly priority = 20
|
|
16
|
+
|
|
17
|
+
override detect(registry: RegistryConfig): boolean {
|
|
18
|
+
if (registry.type !== 'npm') {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const regName = (registry.name || '').toLowerCase()
|
|
23
|
+
if (regName === 'github' || regName.startsWith('github-')) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const server = (registry.server || '').toLowerCase()
|
|
28
|
+
try {
|
|
29
|
+
const parsed = new URL(server.includes('://') ? server : `https://${server}`)
|
|
30
|
+
const host = parsed.hostname.toLowerCase()
|
|
31
|
+
return host === 'npm.pkg.github.com' || GitHubClient.isGitHubHost(host)
|
|
32
|
+
} catch {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override normalizeRegistryUrl(server: string, _namespace?: string): string {
|
|
38
|
+
let url = (server || '').trim()
|
|
39
|
+
if (!url || url === 'github') {
|
|
40
|
+
return 'https://npm.pkg.github.com/'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!/^https?:\/\//i.test(url)) {
|
|
44
|
+
url = `https://${url}`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return url.endsWith('/') ? url : `${url}/`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected override async doPublish(
|
|
51
|
+
artifact: ArtifactPayload,
|
|
52
|
+
registry: RegistryConfig,
|
|
53
|
+
options?: PublishOptions
|
|
54
|
+
): Promise<{
|
|
55
|
+
ref: string
|
|
56
|
+
metadata?: Record<string, unknown>
|
|
57
|
+
}> {
|
|
58
|
+
if (!artifact.name.startsWith('@')) {
|
|
59
|
+
this.log(
|
|
60
|
+
` âš Notice: Package "${artifact.name}" is not scoped. GitHub Packages requires scoped package names (e.g. @owner/repo).`
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return super.doPublish(artifact, registry, options)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
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 { GitHubClient } from '../client/GitHubClient.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GitHub Releases (Generic Artifact / Asset) Publisher.
|
|
10
|
+
*
|
|
11
|
+
* Publishes arbitrary binary artifacts, distribution tarballs (.tar.gz),
|
|
12
|
+
* or zip files to GitHub Releases under the specified version tag.
|
|
13
|
+
*/
|
|
14
|
+
export class GitHubReleasePublisher extends BasePublisher {
|
|
15
|
+
readonly id = 'github-release'
|
|
16
|
+
readonly name = 'GitHub Releases'
|
|
17
|
+
readonly platform = 'github'
|
|
18
|
+
readonly artifactType = 'generic'
|
|
19
|
+
override readonly priority = 20
|
|
20
|
+
|
|
21
|
+
override detect(registry: RegistryConfig): boolean {
|
|
22
|
+
if (registry.type !== 'generic' && registry.type !== 'archive' && registry.type !== 'release') {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const regName = (registry.name || '').toLowerCase()
|
|
27
|
+
if (regName === 'github' || regName === 'github-release' || regName.startsWith('github-')) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const server = (registry.server || '').toLowerCase()
|
|
32
|
+
try {
|
|
33
|
+
const parsed = new URL(server.includes('://') ? server : `https://${server}`)
|
|
34
|
+
return GitHubClient.isGitHubHost(parsed.hostname)
|
|
35
|
+
} catch {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override buildTargetRef(artifact: ArtifactPayload, registry: RegistryConfig): string {
|
|
41
|
+
const version = artifact.tags && artifact.tags.length > 0 ? artifact.tags[0] : 'v1.0.0'
|
|
42
|
+
const tagName = version.startsWith('v') || /^\d/.test(version) ? version : `v${version}`
|
|
43
|
+
const filename = artifact.source ? path.basename(artifact.source) : artifact.name
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const { owner, repo } = this.resolveOwnerRepo(registry)
|
|
47
|
+
return `${owner}/${repo}@${tagName}#${filename}`
|
|
48
|
+
} catch {
|
|
49
|
+
return `${artifact.name}@${tagName}#${filename}`
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected resolveOwnerRepo(registry: RegistryConfig): { owner: string; repo: string } {
|
|
54
|
+
let namespace = registry.namespace || ''
|
|
55
|
+
let owner = ''
|
|
56
|
+
let repo = ''
|
|
57
|
+
|
|
58
|
+
if (namespace.includes('/')) {
|
|
59
|
+
const parts = namespace.split('/')
|
|
60
|
+
owner = parts[0]
|
|
61
|
+
repo = parts[1]
|
|
62
|
+
} else if (registry.server && registry.server.includes('/')) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = GitHubClient.parseRepo(registry.server)
|
|
65
|
+
owner = parsed.owner
|
|
66
|
+
repo = parsed.repo
|
|
67
|
+
} catch {
|
|
68
|
+
// fall through
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!owner || !repo) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`GitHub Releases publisher requires repository namespace in "owner/repo" format (got "${registry.namespace || registry.server}").`
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { owner, repo }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override async exists(
|
|
82
|
+
artifact: ArtifactPayload,
|
|
83
|
+
registry: RegistryConfig,
|
|
84
|
+
options?: PublishOptions
|
|
85
|
+
): Promise<boolean> {
|
|
86
|
+
const creds = this.resolveCredentials(registry)
|
|
87
|
+
const token = creds.token || creds.password
|
|
88
|
+
|
|
89
|
+
let owner = ''
|
|
90
|
+
let repo = ''
|
|
91
|
+
try {
|
|
92
|
+
const resolved = this.resolveOwnerRepo(registry)
|
|
93
|
+
owner = resolved.owner
|
|
94
|
+
repo = resolved.repo
|
|
95
|
+
} catch {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const client = new GitHubClient({
|
|
100
|
+
baseUrl: registry.server && registry.server.startsWith('http') ? registry.server : undefined,
|
|
101
|
+
token,
|
|
102
|
+
timeoutMs: options?.timeoutMs
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const version = artifact.tags && artifact.tags.length > 0 ? artifact.tags[0] : 'v1.0.0'
|
|
106
|
+
const tagName = version.startsWith('v') || /^\d/.test(version) ? version : `v${version}`
|
|
107
|
+
const filename = artifact.source ? path.basename(artifact.source) : artifact.name
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const release = await client.getReleaseByTag(owner, repo, tagName)
|
|
111
|
+
if (!release) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
return Boolean(release.assets?.some(a => a.name === filename))
|
|
115
|
+
} catch (err: any) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Failed to check artifact existence in GitHub Releases: ${client.maskSecrets(err?.message || String(err))}`
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override async download(
|
|
123
|
+
artifact: ArtifactPayload,
|
|
124
|
+
registry: RegistryConfig,
|
|
125
|
+
dest: string,
|
|
126
|
+
options?: DownloadOptions
|
|
127
|
+
): Promise<DownloadResult> {
|
|
128
|
+
const creds = this.resolveCredentials(registry)
|
|
129
|
+
const { owner, repo } = this.resolveOwnerRepo(registry)
|
|
130
|
+
const client = new GitHubClient({
|
|
131
|
+
baseUrl: registry.server && registry.server.startsWith('http') ? registry.server : undefined,
|
|
132
|
+
token: creds.token || creds.password,
|
|
133
|
+
timeoutMs: options?.timeoutMs
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const version = artifact.tags?.[0] || 'v1.0.0'
|
|
137
|
+
const tagName = version.startsWith('v') || /^\d/.test(version) ? version : `v${version}`
|
|
138
|
+
const filename = artifact.source ? path.basename(artifact.source) : artifact.name
|
|
139
|
+
const what = `${artifact.name} ${version} (${filename})`
|
|
140
|
+
|
|
141
|
+
const release = await client.getReleaseByTag(owner, repo, tagName)
|
|
142
|
+
const asset = release?.assets?.find(a => a.name === filename)
|
|
143
|
+
if (!asset) {
|
|
144
|
+
throw new Error(`${what} not found in GitHub release ${owner}/${repo}@${tagName}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// The asset API answers with a redirect to storage when asked for octet-stream
|
|
148
|
+
const assetUrl = client.getRepoApiUrl(owner, repo, `/releases/assets/${asset.id}`)
|
|
149
|
+
this.log(` 📥 Downloading ${what} from GitHub release ${owner}/${repo}@${tagName}`)
|
|
150
|
+
const res = await client.request(assetUrl, { headers: { Accept: 'application/octet-stream' } })
|
|
151
|
+
if (!res.ok) {
|
|
152
|
+
throw await this.downloadError(res, what, assetUrl)
|
|
153
|
+
}
|
|
154
|
+
return this.saveResponse(res, dest, assetUrl)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async login(registry: RegistryConfig, _options?: PublishOptions): Promise<void> {
|
|
158
|
+
const creds = this.resolveCredentials(registry)
|
|
159
|
+
if (!creds.token && !creds.password) {
|
|
160
|
+
this.log(` ℹ No auth token configured for GitHub Releases ${registry.server}`)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected async doPublish(
|
|
165
|
+
artifact: ArtifactPayload,
|
|
166
|
+
registry: RegistryConfig,
|
|
167
|
+
options?: PublishOptions
|
|
168
|
+
): Promise<{
|
|
169
|
+
ref: string
|
|
170
|
+
digest?: string
|
|
171
|
+
metadata?: Record<string, unknown>
|
|
172
|
+
}> {
|
|
173
|
+
const creds = this.resolveCredentials(registry)
|
|
174
|
+
const token = creds.token || creds.password
|
|
175
|
+
const { owner, repo } = this.resolveOwnerRepo(registry)
|
|
176
|
+
|
|
177
|
+
const client = new GitHubClient({
|
|
178
|
+
baseUrl: registry.server && registry.server.startsWith('http') ? registry.server : undefined,
|
|
179
|
+
token,
|
|
180
|
+
timeoutMs: options?.timeoutMs
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const version = artifact.tags && artifact.tags.length > 0 ? artifact.tags[0] : 'v1.0.0'
|
|
184
|
+
const tagName = version.startsWith('v') || /^\d/.test(version) ? version : `v${version}`
|
|
185
|
+
|
|
186
|
+
let filePath = artifact.source
|
|
187
|
+
const stat = await fs.stat(filePath).catch(() => null)
|
|
188
|
+
if (!stat || !stat.isFile()) {
|
|
189
|
+
throw new Error(`GitHub Release asset source must be an existing file: ${filePath}`)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const filename = path.basename(filePath)
|
|
193
|
+
const fileBuffer = await fs.readFile(filePath)
|
|
194
|
+
const digest = `sha256:${crypto.createHash('sha256').update(fileBuffer).digest('hex')}`
|
|
195
|
+
|
|
196
|
+
this.log(` 📤 Uploading release asset "${filename}" to GitHub ${owner}/${repo} release ${tagName}...`)
|
|
197
|
+
|
|
198
|
+
if (options?.dryRun) {
|
|
199
|
+
this.log(` [dryRun] Would upload asset ${filename} (${fileBuffer.length} bytes) to GitHub release ${tagName}`)
|
|
200
|
+
return {
|
|
201
|
+
ref: `${owner}/${repo}@${tagName}#${filename}`,
|
|
202
|
+
digest,
|
|
203
|
+
metadata: { owner, repo, tagName, filename, size: fileBuffer.length }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Get or create release
|
|
208
|
+
let release = await client.getReleaseByTag(owner, repo, tagName)
|
|
209
|
+
if (!release) {
|
|
210
|
+
this.log(` ✨ Creating new GitHub release ${tagName} in ${owner}/${repo}...`)
|
|
211
|
+
release = await client.createRelease(owner, repo, {
|
|
212
|
+
tagName,
|
|
213
|
+
name: artifact.name || tagName,
|
|
214
|
+
body: `Release ${tagName} published via OmniFlow.`
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const uploaded = await client.uploadReleaseAsset(release.upload_url, filename, fileBuffer)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
ref: `${owner}/${repo}@${tagName}#${filename}`,
|
|
222
|
+
digest,
|
|
223
|
+
metadata: {
|
|
224
|
+
owner,
|
|
225
|
+
repo,
|
|
226
|
+
tagName,
|
|
227
|
+
filename,
|
|
228
|
+
assetId: uploaded.id,
|
|
229
|
+
downloadUrl: uploaded.browser_download_url,
|
|
230
|
+
releaseUrl: release.html_url,
|
|
231
|
+
size: fileBuffer.length
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Convenient alias
|
|
238
|
+
export { GitHubReleasePublisher as GitHubGenericPublisher }
|