feat(s3): create bucket interactively during auth when unconfigured

Problem: when a user runs `:Pending s3 auth` with no bucket configured,
auth succeeds but offers no way to create the bucket. The user must
manually run `aws s3api create-bucket` and update their config.

Solution: add `util.input()` coroutine-aware prompt wrapper and a
`create_bucket()` flow in `s3.lua` that prompts for bucket name and
region, handles the `us-east-1` LocationConstraint quirk, and logs a
config snippet on success. Called automatically from `auth()` when
`sync.s3.bucket` is absent.
This commit is contained in:
Barrett Ruth 2026-03-08 20:16:00 -04:00
parent ee75e6844e
commit 58804fcfc7
3 changed files with 303 additions and 0 deletions

View file

@ -66,6 +66,49 @@ local function ensure_sync_id(task)
return sync_id
end
local function create_bucket()
local name = util.input({ prompt = 'S3 bucket name: ', default = 'pending.nvim' })
if not name or name == '' then
log.info('s3: bucket creation cancelled')
return
end
local region_cmd = base_cmd()
vim.list_extend(region_cmd, { 'configure', 'get', 'region' })
local region_result = util.system(region_cmd, { text = true })
local default_region = 'us-east-1'
if region_result.code == 0 and region_result.stdout then
local detected = vim.trim(region_result.stdout)
if detected ~= '' then
default_region = detected
end
end
local region = util.input({ prompt = 'AWS region: ', default = default_region })
if not region or region == '' then
region = 'us-east-1'
end
local cmd = base_cmd()
vim.list_extend(cmd, { 's3api', 'create-bucket', '--bucket', name, '--region', region })
if region ~= 'us-east-1' then
vim.list_extend(cmd, { '--create-bucket-configuration', 'LocationConstraint=' .. region })
end
local result = util.system(cmd, { text = true })
if result.code == 0 then
log.info(
's3: bucket created. Add to your config:\n sync = { s3 = { bucket = "'
.. name
.. '", region = "'
.. region
.. '" } }'
)
else
log.error('s3: bucket creation failed — ' .. (result.stderr or 'unknown error'))
end
end
---@param args? string
---@return nil
function M.auth(args)
@ -96,6 +139,10 @@ function M.auth(args)
else
log.info('s3: credentials valid')
end
local s3cfg = get_config()
if not s3cfg or not s3cfg.bucket then
create_bucket()
end
else
local stderr = result.stderr or ''
if stderr:find('SSO') or stderr:find('sso') then

View file

@ -35,6 +35,21 @@ function M.system(args, opts)
return coroutine.yield() --[[@as { code: integer, stdout: string, stderr: string }]]
end
---@param opts? {prompt?: string, default?: string}
---@return string?
function M.input(opts)
local co = coroutine.running()
if not co then
error('util.input() must be called inside a coroutine')
end
vim.ui.input(opts or {}, function(result)
vim.schedule(function()
coroutine.resume(co, result)
end)
end)
return coroutine.yield() --[[@as string?]]
end
---@param name string
---@param fn fun(): nil
function M.with_guard(name, fn)