2023-06-27 07:27:09 -05:00
|
|
|
import { basename, join } from 'node:path'
|
|
|
|
import { createWriteStream } from 'node:fs'
|
2023-04-17 05:17:37 -05:00
|
|
|
import { normalize } from 'node:path/posix'
|
2023-06-27 07:27:09 -05:00
|
|
|
import { parse as parseContentType } from 'content-type'
|
2024-01-09 07:24:32 -06:00
|
|
|
import { pipeline } from 'node:stream'
|
|
|
|
import { pipeline as pPipeline } from 'node:stream/promises'
|
|
|
|
import { readChunk } from '@vates/read-chunk'
|
2023-04-17 05:17:37 -05:00
|
|
|
import getopts from 'getopts'
|
|
|
|
import hrp from 'http-request-plus'
|
|
|
|
import merge from 'lodash/merge.js'
|
2024-01-31 08:02:17 -06:00
|
|
|
import set from 'lodash/set.js'
|
2024-01-09 07:24:32 -06:00
|
|
|
import split2 from 'split2'
|
2023-04-17 05:17:37 -05:00
|
|
|
|
|
|
|
import * as config from './config.mjs'
|
|
|
|
|
|
|
|
const PREFIX = '/rest/v0/'
|
|
|
|
|
|
|
|
function addPrefix(suffix) {
|
|
|
|
const path = normalize(PREFIX + stripPrefix(suffix))
|
|
|
|
if (!path.startsWith(PREFIX)) {
|
|
|
|
throw new Error('invalid path')
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
2024-01-09 07:24:32 -06:00
|
|
|
const noop = Function.prototype
|
|
|
|
|
2023-04-17 05:17:37 -05:00
|
|
|
function parseParams(args) {
|
|
|
|
const params = {}
|
|
|
|
for (const arg of args) {
|
|
|
|
const i = arg.indexOf('=')
|
|
|
|
if (i === -1) {
|
2024-01-31 08:02:17 -06:00
|
|
|
set(params, arg, '')
|
2023-04-17 05:17:37 -05:00
|
|
|
} else {
|
2023-06-26 09:21:01 -05:00
|
|
|
const value = arg.slice(i + 1)
|
2024-01-31 08:02:17 -06:00
|
|
|
set(params, arg.slice(0, i), value.startsWith('json:') ? JSON.parse(value.slice(5)) : value)
|
2023-04-17 05:17:37 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return params
|
|
|
|
}
|
|
|
|
|
|
|
|
function stripPrefix(path) {
|
|
|
|
path = normalize('/' + path)
|
|
|
|
return path.startsWith(PREFIX) ? path.slice(PREFIX.length) : path
|
|
|
|
}
|
|
|
|
|
|
|
|
const COMMANDS = {
|
|
|
|
async del(args) {
|
|
|
|
const {
|
|
|
|
_: [path = ''],
|
|
|
|
} = getopts(args)
|
|
|
|
const response = await this.exec(path, { method: 'DELETE' })
|
|
|
|
return await response.text()
|
|
|
|
},
|
|
|
|
|
2023-06-27 07:27:09 -05:00
|
|
|
async get(args) {
|
|
|
|
const {
|
|
|
|
_: [path, ...rest],
|
|
|
|
output,
|
|
|
|
} = getopts(args, {
|
|
|
|
alias: { output: 'o' },
|
|
|
|
string: 'output',
|
|
|
|
stopEarly: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
const response = await this.exec(path, { query: parseParams(rest) })
|
|
|
|
|
|
|
|
if (output !== '') {
|
2024-01-09 07:24:32 -06:00
|
|
|
return pPipeline(
|
2023-06-27 07:27:09 -05:00
|
|
|
response,
|
|
|
|
output === '-'
|
|
|
|
? process.stdout
|
|
|
|
: createWriteStream(output.endsWith('/') ? join(output, basename(path)) : output, { flags: 'wx' })
|
|
|
|
)
|
|
|
|
}
|
2023-04-17 05:17:37 -05:00
|
|
|
|
2023-06-27 07:27:09 -05:00
|
|
|
const { type } = parseContentType(response)
|
|
|
|
if (type === 'application/json') {
|
|
|
|
const result = await response.json()
|
2023-04-17 05:17:37 -05:00
|
|
|
|
2023-06-27 07:27:09 -05:00
|
|
|
if (Array.isArray(result)) {
|
|
|
|
for (let i = 0, n = result.length; i < n; ++i) {
|
|
|
|
const value = result[i]
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
result[i] = stripPrefix(value)
|
|
|
|
} else if (value != null && typeof value.href === 'string') {
|
|
|
|
value.href = stripPrefix(value.href)
|
|
|
|
}
|
2023-04-17 05:17:37 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-27 07:27:09 -05:00
|
|
|
return this.json ? JSON.stringify(result, null, 2) : result
|
2024-01-09 07:24:32 -06:00
|
|
|
} else if (type === 'application/x-ndjson') {
|
|
|
|
const lines = pipeline(response, split2(), noop)
|
|
|
|
let line
|
|
|
|
while ((line = await readChunk(lines)) !== null) {
|
|
|
|
const data = JSON.parse(line)
|
|
|
|
console.log(this.json ? JSON.stringify(data, null, 2) : data)
|
|
|
|
}
|
2023-06-27 07:27:09 -05:00
|
|
|
} else {
|
|
|
|
throw new Error('unsupported content-type ' + type)
|
|
|
|
}
|
2023-04-17 05:17:37 -05:00
|
|
|
},
|
|
|
|
|
2023-06-26 09:08:47 -05:00
|
|
|
async patch([path, ...params]) {
|
|
|
|
const response = await this.exec(path, {
|
|
|
|
body: JSON.stringify(parseParams(params)),
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json',
|
|
|
|
},
|
|
|
|
method: 'PATCH',
|
|
|
|
})
|
|
|
|
|
|
|
|
return await response.text()
|
|
|
|
},
|
|
|
|
|
2023-04-17 05:17:37 -05:00
|
|
|
async post([path, ...params]) {
|
|
|
|
const response = await this.exec(path, {
|
|
|
|
body: JSON.stringify(parseParams(params)),
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json',
|
|
|
|
},
|
|
|
|
method: 'POST',
|
|
|
|
})
|
|
|
|
|
|
|
|
return stripPrefix(await response.text())
|
|
|
|
},
|
2023-11-21 07:07:20 -06:00
|
|
|
|
|
|
|
async put([path, ...params]) {
|
|
|
|
const response = await this.exec(path, {
|
|
|
|
body: JSON.stringify(parseParams(params)),
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json',
|
|
|
|
},
|
|
|
|
method: 'PUT',
|
|
|
|
})
|
|
|
|
|
|
|
|
return stripPrefix(await response.text())
|
|
|
|
},
|
2023-04-17 05:17:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function rest(args) {
|
|
|
|
const {
|
|
|
|
json = false,
|
|
|
|
_: [command, ...rest],
|
|
|
|
} = getopts(args, { boolean: ['json'], stopEarly: true })
|
|
|
|
|
|
|
|
const { allowUnauthorized, server, token } = await config.load()
|
|
|
|
|
2023-12-22 04:33:08 -06:00
|
|
|
if (server === undefined) {
|
|
|
|
const errorMessage =
|
|
|
|
'Please use `xo-cli --register` to associate with an XO instance first.\n\nSee `xo-cli --help` for more info.'
|
|
|
|
throw errorMessage
|
|
|
|
}
|
|
|
|
|
2023-04-17 05:17:37 -05:00
|
|
|
const baseUrl = server
|
|
|
|
const baseOpts = {
|
|
|
|
headers: {
|
|
|
|
cookie: 'authenticationToken=' + token,
|
|
|
|
},
|
|
|
|
rejectUnauthorized: !allowUnauthorized,
|
|
|
|
}
|
|
|
|
|
|
|
|
if (command === undefined || !(command in COMMANDS)) {
|
|
|
|
return console.log('Available commands: ', Object.keys(COMMANDS).sort().join(', '))
|
|
|
|
}
|
|
|
|
|
|
|
|
return COMMANDS[command].call(
|
|
|
|
{
|
2024-01-24 04:23:43 -06:00
|
|
|
async exec(path, { query = {}, ...opts } = {}) {
|
2023-04-17 05:17:37 -05:00
|
|
|
const url = new URL(baseUrl)
|
2023-05-17 04:27:15 -05:00
|
|
|
|
|
|
|
const i = path.indexOf('?')
|
|
|
|
let pathname
|
|
|
|
if (i === -1) {
|
|
|
|
pathname = path
|
|
|
|
} else {
|
|
|
|
pathname = path.slice(0, i)
|
|
|
|
url.search = path.slice(i)
|
|
|
|
}
|
|
|
|
url.pathname = addPrefix(pathname)
|
|
|
|
|
2023-04-17 05:17:37 -05:00
|
|
|
for (const [name, value] of Object.entries(query)) {
|
|
|
|
if (value !== undefined) {
|
|
|
|
url.searchParams.set(name, value)
|
|
|
|
}
|
|
|
|
}
|
2023-05-17 04:27:15 -05:00
|
|
|
|
2024-01-24 04:23:43 -06:00
|
|
|
try {
|
|
|
|
return await hrp(url, merge({}, baseOpts, opts))
|
|
|
|
} catch (error) {
|
|
|
|
const { response } = error
|
|
|
|
if (response === undefined) {
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
|
|
|
|
console.error(response.statusCode, response.statusMessage)
|
|
|
|
throw await response.text()
|
|
|
|
}
|
2023-04-17 05:17:37 -05:00
|
|
|
},
|
|
|
|
json,
|
|
|
|
},
|
|
|
|
rest
|
|
|
|
)
|
|
|
|
}
|