docker-hub-create-repo.mjs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { readFileSync } from 'node:fs'
  2. import { resolve } from 'node:path'
  3. const packageJson = JSON.parse(readFileSync(resolve('package.json'), 'utf8'))
  4. const image = process.env.DOCKER_IMAGE || 'flyfishdev/file-viewer'
  5. const [namespace, repository] = image.split('/')
  6. const username = process.env.DOCKERHUB_USERNAME || process.env.DOCKER_HUB_USERNAME
  7. const token = process.env.DOCKERHUB_TOKEN || process.env.DOCKER_HUB_TOKEN
  8. const bearerToken = process.env.DOCKERHUB_BEARER_TOKEN || process.env.DOCKER_HUB_BEARER_TOKEN
  9. if (!namespace || !repository) {
  10. console.error('[docker-hub] DOCKER_IMAGE must use namespace/repository format.')
  11. process.exit(1)
  12. }
  13. const hubFetch = async (url, options = {}) => {
  14. const response = await fetch(url, {
  15. ...options,
  16. headers: {
  17. accept: 'application/json',
  18. ...(options.body ? { 'content-type': 'application/json' } : {}),
  19. ...(options.headers || {})
  20. }
  21. })
  22. const text = await response.text()
  23. let body = null
  24. try {
  25. body = text ? JSON.parse(text) : null
  26. } catch {
  27. body = text
  28. }
  29. return { response, body }
  30. }
  31. const formatErrorBody = body => {
  32. if (!body) {
  33. return ''
  34. }
  35. if (typeof body === 'string') {
  36. return body.slice(0, 500)
  37. }
  38. return JSON.stringify(body).slice(0, 500)
  39. }
  40. const getBearerToken = async () => {
  41. if (bearerToken) {
  42. return bearerToken
  43. }
  44. if (!username || !token) {
  45. console.error('[docker-hub] Missing DOCKERHUB_USERNAME and DOCKERHUB_TOKEN. Use a Docker Hub PAT with repository write/admin permission.')
  46. process.exit(1)
  47. }
  48. const { response, body } = await hubFetch('https://hub.docker.com/v2/auth/token', {
  49. method: 'POST',
  50. body: JSON.stringify({
  51. identifier: username,
  52. secret: token
  53. })
  54. })
  55. if (!response.ok || !body?.access_token) {
  56. console.error(`[docker-hub] Failed to create auth token: HTTP ${response.status} ${formatErrorBody(body)}`)
  57. process.exit(1)
  58. }
  59. return body.access_token
  60. }
  61. const authToken = await getBearerToken()
  62. const authorization = { authorization: `Bearer ${authToken}` }
  63. const repositoryUrl = `https://hub.docker.com/v2/namespaces/${namespace}/repositories/${repository}`
  64. const existing = await hubFetch(repositoryUrl, { headers: authorization })
  65. if (existing.response.ok) {
  66. console.log(`[docker-hub] Repository already exists: ${namespace}/${repository}`)
  67. process.exit(0)
  68. }
  69. if (existing.response.status !== 404) {
  70. console.error(`[docker-hub] Repository check failed: HTTP ${existing.response.status} ${formatErrorBody(existing.body)}`)
  71. process.exit(1)
  72. }
  73. const description = process.env.DOCKERHUB_DESCRIPTION || 'Pure web multi-format file viewer demo.'
  74. const fullDescription = process.env.DOCKERHUB_FULL_DESCRIPTION ||
  75. `Flyfish Viewer ${packageJson.version}: static Docker image for the demo and document compare page.`
  76. const created = await hubFetch(`https://hub.docker.com/v2/namespaces/${namespace}/repositories`, {
  77. method: 'POST',
  78. headers: authorization,
  79. body: JSON.stringify({
  80. name: repository,
  81. namespace,
  82. description,
  83. full_description: fullDescription,
  84. registry: 'docker.io',
  85. is_private: false
  86. })
  87. })
  88. if (!created.response.ok) {
  89. console.error(`[docker-hub] Repository create failed: HTTP ${created.response.status} ${formatErrorBody(created.body)}`)
  90. process.exit(1)
  91. }
  92. console.log(`[docker-hub] Repository created: ${namespace}/${repository}`)