gitoriaLog in with ident

antcolony

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit3a4d03243a4d0324antcolony#37: a too-long report gets up to 3 fix tries, finished work is never thrown away for lengthmre3a4d0324/lib/deploy.hl

7.3 KB

  1. // deploy.hl — mission 027 (ticket antcolony#1, first live batch 2026-09-24): the DEPLOY GATE's two commands.
  2. //
  3. // Why: the creator can only test on the live site. A passed, complete report on a project whose registry has `live`
  4. // is therefore NOT "ready for you to test" (report.hl deployGate): it gets ONE line "Built — goes live with the next
  5. // deploy." (machine line `colony-report: S · sha256 H · controller pass · ready to deploy`) and the ticket stays
  6. // `in progress` — out of `next`, its lease never expires (lease.hl readyOf / leaseOf kind `ready`).
  7. //
  8. // ./colony ready every ticket waiting for a deploy (GETs only), grouped by project, with the live
  9. // site and the deploy note of the registry
  10. // ./colony deployed <project> <n> after the architect deployed: the readable comment — "Now live on <site> — <result>",
  11. // **Test:** as a numbered list, ≤ 3 details (questions, problems filed elsewhere,
  12. // Decided, not done …), machine line `colony-deployed: S · sha256 H` — from the
  13. // session's runs/<S>/report.json on THIS host; then `in progress → awaiting creator`
  14. // ("Ready for you to test — see the last comment."). Idempotent: a second call writes
  15. // nothing (a missing state change is repaired).
  16. import { env } from 'hl:proc'
  17. import { loadRegistry } from './registry.hl'
  18. import { getJson, postJson, readToken, ticketPath, pageUrl, allTickets } from './tickets.hl'
  19. import { readyOf } from './lease.hl'
  20. import { survey } from './next.hl'
  21. import { deployedCommentOf, siteName, linkOf, refLabel } from './report.hl'
  22. import { sha256 } from 'hl:crypto'
  23. import { NL, readJson, fromCwd, positionals } from './util.hl'
  24. static runsDirOf = () => {
  25. let r = env('COLONY_RUNS_DIR')
  26. if (r != null && r != '') { return fromCwd(r) }
  27. let home = env('COLONY_HOME')
  28. return home == null || home == '' ? './runs' : home + '/runs'
  29. }
  30. // `./colony ready`
  31. static runReady = (argv) => {
  32. let sv = survey()
  33. if (sv.error != null) {
  34. console.log('ready: ' + sv.error)
  35. return false
  36. }
  37. let n = 0
  38. for (e of sv.notEligible) {
  39. let t = e.t
  40. if (t.state == 'in progress' && t.readySession != null) {
  41. n = n + 1
  42. let m = sv.reg.projects[t.project]
  43. let live = m == null || m.live == null ? '(no live entry any more)' : (m.live.url != null ? m.live.url : m.live.host + ':' + m.live.folder)
  44. let dep = m == null || m.deploy == null ? '' : ' · deploy: ' + m.deploy
  45. console.log('READY ' + t.project + t.ref + ' "' + t.subject + '" — built by session ' + t.readySession + ' · live ' + live + dep + ' · after the deploy: ./colony deployed ' + t.project + ' ' + t.number + ' · ' + pageUrl(t))
  46. }
  47. }
  48. console.log('READY: ' + n + ' ticket(s) waiting for a deploy' + (n > 0 ? ' — deploy, then `./colony deployed <project> <n>` for each' : ''))
  49. return true
  50. }
  51. // the filed questions / issues of a session (by their `source`) → { asked: [link], filed: [{ link, blocks, unknown }] }
  52. static filedOf = (session, report) => {
  53. let asked = []
  54. let filed = []
  55. let all = allTickets()
  56. if (all.error != null) { return { asked = asked filed = filed } }
  57. let pre = 'colony-report:' + session + ':'
  58. let qi = 0
  59. while (qi < report.questions.length) {
  60. for (t of all.tickets) { if (t.source == pre + 'questions:' + qi) { asked.push(linkOf(t)) } }
  61. qi = qi + 1
  62. }
  63. let ii = 0
  64. while (ii < report.issues.length) {
  65. for (t of all.tickets) {
  66. if (t.source == pre + 'issue:' + ii) { filed.push({ link = linkOf(t) blocks = report.issues[ii].blocks unknown = false }) }
  67. else if (t.source == pre + 'question:' + ii) { filed.push({ link = linkOf(t) blocks = false unknown = true }) }
  68. }
  69. ii = ii + 1
  70. }
  71. return { asked = asked filed = filed }
  72. }
  73. // `./colony deployed <project> <n>`
  74. static runDeployed = (argv) => {
  75. let pos = positionals(argv)
  76. if (pos.length != 3) {
  77. console.log('usage: deployed <project> <number>')
  78. return false
  79. }
  80. let project = pos[1]
  81. let number = pos[2]
  82. let ref = project + '#' + number
  83. let tk = readToken()
  84. if (tk.error != null) {
  85. console.log('deployed: REFUSED — ' + tk.error)
  86. return false
  87. }
  88. let got = getJson(ticketPath(project, number))
  89. if (got.status != 200) {
  90. console.log('deployed: REFUSED — ticket ' + ref + ' → ' + got.status)
  91. return false
  92. }
  93. let t = got.json.ticket
  94. let events = got.json.events
  95. // already done? (idempotent: the deployed comment of the last ready session exists)
  96. let lastReady = null
  97. for (e of events) {
  98. if (e.kind == 'comment' && e.text != null) {
  99. for (l of e.text.split(NL)) {
  100. let x = l.trim()
  101. if (x.startsWith('colony-report: ') && x.endsWith(' · ready to deploy')) { lastReady = x.slice(15).split(' · ')[0].trim() }
  102. }
  103. }
  104. }
  105. let have = false
  106. if (lastReady != null) {
  107. for (e of events) { if (e.kind == 'comment' && e.text != null && e.text.includes('colony-deployed: ' + lastReady + ' ·')) { have = true } }
  108. }
  109. let session = readyOf(events)
  110. if (session == null && !have) {
  111. console.log('deployed: REFUSED — ' + ref + ' is "' + t.state + '" and not waiting for a deploy (no "ready to deploy" report of a colony session' + (lastReady != null ? '; the last one, ' + lastReady + ', is void: the ticket left "in progress" or another session started' : '') + '). Nothing written.')
  112. return false
  113. }
  114. if (session == null) { session = lastReady }
  115. let reg = loadRegistry()
  116. let m = reg.projects[project]
  117. let site = m != null && m.live != null && m.live.url != null ? siteName(m.live.url) : project
  118. let token = tk.token
  119. if (have) {
  120. console.log(' HAVE the deployed comment of session ' + session + ' on ' + ref + ' — not written again')
  121. } else {
  122. let dir = runsDirOf() + '/' + session
  123. let rj = readJson(dir + '/report.json')
  124. if (rj.error != null) {
  125. console.log('deployed: REFUSED — the report of session ' + session + ' is not on this host (' + rj.error + '); run `deployed` where the session ran. Nothing written.')
  126. return false
  127. }
  128. let r = rj.value
  129. if (r.session != session || r.ticket != ref) {
  130. console.log('deployed: REFUSED — ' + dir + '/report.json is for ' + r.ticket + ' session ' + r.session + ', not ' + ref + ' session ' + session + '. Nothing written.')
  131. return false
  132. }
  133. let hash = sha256(JSON.stringify(r)).slice(0, 12)
  134. let f = filedOf(session, r)
  135. let text = deployedCommentOf(r, f.filed, f.asked, site, hash)
  136. let c = postJson(ticketPath(project, number) + '/comments', { text = text }, token)
  137. if (c.status != 201) {
  138. console.log('deployed: FAILED — comment on ' + ref + ': ' + c.status + ' ' + c.text)
  139. return false
  140. }
  141. console.log(' POSTED the deployed comment on ' + ref + ' ' + pageUrl(t))
  142. for (l of text.split(NL)) { console.log(' | ' + l) }
  143. t = c.json.ticket
  144. }
  145. if (t.state == 'in progress') {
  146. let s = postJson(ticketPath(project, number) + '/state', { state = 'awaiting creator' text = 'Ready for you to test — see the last comment.' }, token)
  147. if (s.status != 201) {
  148. console.log('deployed: FAILED — state of ' + ref + ': ' + s.status + ' ' + s.text + ' — re-run (idempotent)')
  149. return false
  150. }
  151. console.log(' STATE ' + ref + ': in progress → awaiting creator')
  152. } else {
  153. console.log(' STATE left as "' + t.state + '"')
  154. }
  155. console.log('DEPLOYED — ' + ref + ' (session ' + session + ', live on ' + site + ')' + (have ? ': nothing new' : ''))
  156. return true
  157. }

Branches

Latest commits

  • 3a4d0324antcolony#37: a too-long report gets up to 3 fix tries, finished work is never thrown away for lengthmre
  • a6af7883tracker: worker box sees calendar.worldapi.org (login to copy)mre
  • c613d26btemplates: bridges to external components (login.js for ident's selector) are allowed (creator 2026-09-27)mre
  • 9062978ctracker: worker box sees /media/STORAGE/projects/old-tracker read-only (tracker#2 source data)mre
  • 7f9660eeState of 2026-09-27, before the move to gitoriamre