Skip to content

Commit 658aac3

Browse files
authored
Merge pull request #62 from InsForge/feat/service-rename
feat: add service rename command
2 parents 98fe669 + 95d960e commit 658aac3

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

src/commands/services.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { info, printJson, handleApproval, renderNextActions } from '../util.js'
44

55
export const SERVICE_TYPES = ['postgres', 'storage', 'compute'] as const
66
export type ServiceType = (typeof SERVICE_TYPES)[number]
7+
const SERVICE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,38}$/
78

89
export function q(branch?: string): string {
910
return branch ? `?branch=${encodeURIComponent(branch)}` : ''
@@ -16,6 +17,10 @@ export function assertType(type: string, allowed: readonly string[] = SERVICE_TY
1617
if (!allowed.includes(type)) throw new Error(`type must be ${allowed.join('|')}`)
1718
}
1819

20+
export function assertServiceName(name: string): void {
21+
if (!SERVICE_NAME_RE.test(name)) throw new Error('service name must be lower-kebab (a-z, 0-9, -)')
22+
}
23+
1924
// Parse a positive-integer machine count.
2025
export function parseCount(raw: string): number {
2126
const n = Number(raw)
@@ -107,6 +112,20 @@ export async function servicesRemove(type: string, name: string, opts: { branch?
107112
info(`removed ${type} service ${name} from ${branch ?? 'default'}`)
108113
}
109114

115+
export async function servicesRename(type: string, name: string, newName: string, opts: { branch?: string; json?: boolean } = {}): Promise<void> {
116+
assertType(type)
117+
assertServiceName(newName)
118+
const api = await ApiClient.load()
119+
const p = await requireProject()
120+
const branch = opts.branch ?? p.branch
121+
const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(branch)}`)
122+
const id = resolveServiceId(services, type, name)
123+
const res = await api.rawRequest('POST', `/projects/${p.projectId}/services/${id}/rename`, { name: newName })
124+
if (handleApproval(res)) return
125+
if (opts.json) return printJson(res.body.service)
126+
info(`renamed ${type} service ${name} to ${newName}`)
127+
}
128+
110129
// Validate a bucket access-mode argument.
111130
export function parseAccess(raw: string): boolean {
112131
if (raw === 'public') return true

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ svc.command('list').option('--json').option('--branch <branch>', 'branch (defaul
115115
svc.command('remove <type> <name>').description('Remove a service and destroy its resources')
116116
.option('--branch <branch>', 'branch (default: current)')
117117
.action(guard((type, name, o) => services.servicesRemove(type, name, o)))
118+
svc.command('rename <type> <name> <new-name>').description('Rename a service and re-key its managed secret names')
119+
.option('--json').option('--branch <branch>', 'branch (default: current)')
120+
.action(guard((type, name, newName, o) => services.servicesRename(type, name, newName, o)))
118121
svc.command('set-access <type> <name> <access>').description('Set a storage service bucket access mode (access: public|private)')
119122
.option('--json').action(guard((type, name, access, o) => services.servicesSetAccess(type, name, access, o)))
120123
svc.command('scale <type> <name> <number> [region]').description('Set a compute service machine count (paid plans only)')

test/services.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from 'vitest'
22
import {
3-
assertType, parseCount, parseAccess, resolveServiceId, resolveComputeServiceId, SERVICE_TYPES,
3+
assertType, assertServiceName, parseCount, parseAccess, resolveServiceId, resolveComputeServiceId, SERVICE_TYPES,
44
servicesAddRequestBody, servicesAdd, serviceListLine,
55
} from '../src/commands/services.js'
66

@@ -31,6 +31,16 @@ describe('parseCount', () => {
3131
})
3232
})
3333

34+
describe('assertServiceName', () => {
35+
it('accepts lower-kebab service names', () => {
36+
expect(() => assertServiceName('primary-db')).not.toThrow()
37+
})
38+
it('rejects names outside the platform service-name rule', () => {
39+
expect(() => assertServiceName('Primary')).toThrow(/lower-kebab/)
40+
expect(() => assertServiceName('-db')).toThrow(/lower-kebab/)
41+
})
42+
})
43+
3444
describe('parseAccess', () => {
3545
it('maps public/private to a boolean', () => {
3646
expect(parseAccess('public')).toBe(true)

0 commit comments

Comments
 (0)