Commit

Money in billing's messages reads to the cent, and the deploy reads migrations again after a failure

- Balances and amounts owed say $3.99, not $3.987 (cents(); dollars() keeps its finer digits for prices under a cent). - deploy.mjs lists pending migrations once more when the first try fails: Cloudflare answered 403 while Wrangler's login refreshed, and the packages deploy stopped with nothing deployed.

syntaqxcommitted Parentbcf63dcBrowse files
3 files+23−60/3 viewed
+5−1
161161
162162 /** Pending migrations of a unit's database: { pending } or { error }. */
163163 export async function pendingMigrations(unit) {
164− const found = await wrangler(["d1", "migrations", "list", unit.d1.database, "--remote"], { cwd: join(ROOT, unit.path) });
164+ const list = () => wrangler(["d1", "migrations", "list", unit.d1.database, "--remote"], { cwd: join(ROOT, unit.path) });
165+ // Once more after a failure: Cloudflare's API sometimes answers 403
166+ // while Wrangler's login refreshes (seen on 2026-10-06).
167+ let found = await list();
168+ if (found.code !== 0) found = await list();
165169 if (found.code !== 0) return { error: lastLines(found.out) };
166170 return { pending: pendingFrom(found.out) };
167171 }
+5−5
2727
2828 use crate::Billing;
2929 use crate::accounts::own_account;
30−use crate::features::dollars;
30+use crate::features::cents;
3131
3232 /// `repo.transferred` (see `g1t_kit::transfer`): `?1` the repository's
3333 /// path now, `?2` a path it had. Ledger rows, runs and holds keep the path
7979 if facts.balance_micros > 0 {
8080 return Err(format!(
8181 "{ws} has {} of prepaid credit left, which would be lost. Spend it, or write to support@g1t.sh about a refund, first.",
82− dollars(facts.balance_micros)
82+ cents(facts.balance_micros)
8383 ));
8484 }
8585 if facts.metering_micros > 0 {
8686 return Err(format!(
8787 "{ws} has {} of usage this month (storage and git operations) that is charged when the month closes. You can delete it from {}.",
88− dollars(facts.metering_micros),
88+ cents(facts.metering_micros),
8989 facts.next_month
9090 ));
9191 }
9494 if !facts.has_card {
9595 return Err(format!(
9696 "{ws} owes {}. Add a card or pay it from the workspace's Billing page first.",
97− dollars(owed)
97+ cents(owed)
9898 ));
9999 }
100100 return Ok(Settle::Charge(owed));
176176 FailureCode::PaymentRequired,
177177 format!(
178178 "The card on file was declined for the {} {workspace} owes. Pay it from the workspace's Billing page first.",
179− dollars(owed)
179+ cents(owed)
180180 ),
181181 ));
182182 }
+13−0
105105 format!("${whole}.{fraction:0<2}")
106106 }
107107
108+/// An amount of money to the cent, as balances and amounts owed read:
109+/// `$3.99`, never `$3.987`. Prices use [`dollars`].
110+pub(crate) fn cents(micros: i64) -> String {
111+ format!("${:.2}", micros as f64 / MICROS_PER_DOLLAR as f64)
112+}
113+
108114 /// `units` at `each` micros a unit, as the pricing page writes it: a
109115 /// build second's price times 60 is the build minute both quote.
110116 pub(crate) fn per_units(each: f64, units: f64) -> String {
685691 }
686692
687693 #[test]
694+ fn amounts_of_money_read_to_the_cent() {
695+ assert_eq!(cents(3_986_990), "$3.99");
696+ assert_eq!(cents(5_000_000), "$5.00");
697+ assert_eq!(cents(4_000), "$0.00");
698+ }
699+
700+ #[test]
688701 fn prices_under_a_cent_keep_their_digits() {
689702 assert_eq!(dollars(1512), "$0.0015");
690703 assert_eq!(dollars(24_000), "$0.024");