1234module cli56import json27import os8import src.build9import src.exchange10import src.fmt11import src.history12import src.machine13import src.release14import src.remote15import src.storage1617pub fn cmd_offer(raw []string) int {18 args := parse('offer', raw, Options{19 values: ['notes']20 flags: ['json', 'builds']21 }) or { return usage_error(err.msg()) }22 23 24 if args.positional.len > 0 {25 return usage_error('offer takes no arguments, not "${args.positional_at(0)}"')26 }27 if args.value_or_none('notes') != none && !args.flag('builds') {28 return usage_error('--notes belongs to --builds')29 }30 store := open_store() or { return refuse(args.json(), err.msg()) }31 defer {32 store.close()33 }34 source := exchange.source_of(store)35 if source != '' {36 host := exchange.source_host(source)37 if !exchange.signed_in_to(host) {38 return refuse(args.json(), 'this repo came from ${source}, which is read-only. Sign in with "koh remote ${host}" if it\'s yours.')39 }40 41 42 mut bucket := this_machine_backend() or { return refuse(args.json(), err.msg()) }43 if _ := exchange.steal(store, mut bucket, exchange.StealOptions{44 theirs: true45 }) {46 exchange.forget_source(store) or { return refuse(args.json(), err.msg()) }47 if !args.json() {48 ok('signed in to ${host}: this repo now syncs with your bucket')49 }50 } else {51 return refuse(args.json(), err.msg())52 }53 }54 mut backend := this_machine_backend() or { return refuse(args.json(), err.msg()) }55 if args.flag('builds') {56 return offer_builds(args, store, mut backend)57 }58 report := exchange.offer(store, mut backend) or { return refuse(args.json(), err.msg()) }59 if args.json() {60 emit_json(json2.encode(OfferedJson{61 repo: report.repo62 saves: report.saves63 objects: report.objects64 events: report.events65 head: report.head66 }))67 return 068 }69 if report.saves + report.objects + report.events == 0 {70 ok('the remote already has everything from [${report.head}]')71 return 072 }73 ok('offered ${report.saves} saves · ${report.objects} objects · ${report.events} events to "${report.repo}"')74 note('remote head is now [${report.head}]')75 return 076}7778798081fn offer_builds(args Args, store &storage.Store, mut backend remote.Backend) int {82 head := history.head(store) or { return refuse(args.json(), exchange.no_saves) }83 if exchange.dirty(store, head) or { true } {84 return refuse(args.json(), 'save or checkpoint first — a release is built from a save')85 }86 report := exchange.offer(store, mut backend) or { return refuse(args.json(), err.msg()) }87 config := build.read_config(store.root) or { return refuse(args.json(), err.msg()) }88 89 release_version := build.project_version(store.root) or {90 return refuse(args.json(), err.msg())91 }92 notes := args.value('notes')93 release.check_notes(notes) or { return refuse(args.json(), err.msg()) }94 published := release.publish(store, mut backend, report.repo, config, release_version, notes) or {95 return refuse(args.json(), err.msg())96 }97 if args.json() {98 emit_json(json2.encode(PublishedJson{99 repo: report.repo100 version: published.version101 files: published.files.map(PublishedFileJson{102 platform: it.platform103 name: it.name104 size: it.size105 blake3: it.blake3106 })107 }))108 return 0109 }110 for file in published.files {111 ok('${file.platform} ${file.name} (${fmt.human_bytes(file.size)})')112 }113 ok('published ${published.version} of "${report.repo}" from [${report.head}]')114 return 0115}116117118pub fn this_machine_backend() !remote.Backend {119 settings := machine.load_remote()!120 if settings.kind == machine.kind_folder {121 122 123 124 if !os.is_dir(settings.path) {125 return error('the remote folder ${settings.path} is not there — is its disk mounted? ("koh remote" changes it)')126 }127 return remote.new_folder(settings.path)!128 }129 credentials := machine.load_credentials()!130 return remote.new_s3(settings.endpoint, settings.bucket, settings.region,131 credentials.access_key, credentials.secret_key)!132}133134135136137pub fn auto_offer(store &storage.Store, quiet bool) {138 if !machine.has_remote() {139 return140 }141 142 143 if exchange.source_of(store) != '' {144 return145 }146 mut backend := this_machine_backend() or {147 if !quiet {148 note('not offered: ${err.msg()}')149 }150 return151 }152 report := exchange.offer(store, mut backend) or {153 if !quiet {154 note('not offered: ${err.msg()}')155 }156 return157 }158 if !quiet && report.saves + report.objects + report.events > 0 {159 note('offered to "${report.repo}" — ${report.saves} saves · ${report.objects} objects')160 }161}162163struct OfferedJson {164 repo string165 saves int166 objects int167 events int168 head string169}170171struct PublishedFileJson {172 platform string173 name string174 size i64175 blake3 string176}177178struct PublishedJson {179 repo string180 version string181 files []PublishedFileJson182}