Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

Commit

Permalink
refactor: move all commands to separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
hanspagel committed Jan 29, 2024
1 parent 0802b9b commit 3554dc7
Show file tree
Hide file tree
Showing 9 changed files with 4,746 additions and 4,700 deletions.
8,490 changes: 4,233 additions & 4,257 deletions packages/cli/openapi.json

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions packages/cli/src/commands/format/FormatCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import fs from 'node:fs'
import { Command } from 'commander'
import kleur from 'kleur'
import { format } from 'prettier'
import { readFile, useGivenFileOrConfiguration } from '../../utils'

export function FormatCommand() {
const cmd = new Command('format')

cmd.description('Format an OpenAPI file')
cmd.argument('[file]', 'file to format')
cmd.action(async (fileArgument: string) => {
const startTime = performance.now()

const file = useGivenFileOrConfiguration(fileArgument)

const fileContent = readFile(file)

if (!fileContent) {
console.error(kleur.red('Couldn’t read file.'))
process.exit(1)
}

const newContent = await format(fileContent, {
semi: false,
parser: 'json',
})

// Replace file content with newContent
fs.writeFileSync(file, newContent, 'utf8')

const endTime = performance.now()

console.log(
kleur.green('File formatted'),
kleur.grey(
`in ${kleur.white(
`${kleur.bold(`${Math.round(endTime - startTime)}`)} ms`,
)}`,
),
)
console.log()
})

return cmd
}
6 changes: 6 additions & 0 deletions packages/cli/src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './format/FormatCommand'
export * from './init/InitCommand'
export * from './mock/MockCommand'
export * from './reference/ReferenceCommand'
export * from './share/ShareCommand'
export * from './validate/ValidateCommand'
83 changes: 83 additions & 0 deletions packages/cli/src/commands/init/InitCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import fs from 'node:fs'
import { Command } from 'commander'
import kleur from 'kleur'
import prompts from 'prompts'
import toml from 'toml-js'

export function InitCommand() {
const cmd = new Command('init')

cmd.description('Create a new `scalar.toml` file')
cmd.option('-f, --file [file]', 'your OpenAPI file')
cmd.action(async ({ file }) => {
// Check if `scalar.toml` already exists
if (fs.existsSync('scalar.toml')) {
console.warn(kleur.yellow('A `scalar.toml` file already exists.'))
console.log()

const { overwrite } = await prompts({
type: 'toggle',
name: 'overwrite',
message: 'Do you want to override the file?',
initial: false,
active: 'yes',
inactive: 'no',
})

if (overwrite === false) {
console.log()
process.exit(1)
}
}

// Ask for the OpenAPI file
const configuration = {
reference: { file: '' },
}

const { input } = file
? {
input: file,
}
: await prompts({
type: 'text',
name: 'input',
message: 'Where is your OpenAPI file?',
initial: './openapi.json',
validate: (input: string) => {
return fs.existsSync(input) ? true : 'File doesn’t exist.'
},
})

configuration.reference.file = input

const content = toml.dump(configuration)

console.log()
console.log(kleur.bold().white(' scalar.toml'))
console.log()
console.log(
content
.trim()
.split('\n')
.map((line) => kleur.grey(` ${line}`))
.join('\n'),
)
console.log()

// Create `scalar.toml` file
fs.writeFileSync('scalar.toml', content)

console.log(kleur.green('Created a new project configuration.'))
console.log(
kleur.white(
`Run ${kleur
.grey()
.bold('scalar --help')} to see all available commands.`,
),
)
console.log()
})

return cmd
}
134 changes: 134 additions & 0 deletions packages/cli/src/commands/mock/MockCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import fs from 'node:fs'
import { serve } from '@hono/node-server'
import { getExampleFromSchema } from '@scalar/api-reference'
import { Hono } from 'hono'
import type { OpenAPI } from 'openapi-types'
import { Command } from 'commander'
import kleur from 'kleur'
import {
getMethodColor,
getOperationByMethodAndPath,
loadOpenApiFile,
useGivenFileOrConfiguration,
} from '../../utils'

export function MockCommand() {
const cmd = new Command('mock')

cmd.description('Mock an API from an OpenAPI file')
cmd.argument('[file]', 'OpenAPI file to mock the server for')
cmd.option('-w, --watch', 'watch the file for changes')
cmd.option('-p, --port <port>', 'set the HTTP port for the mock server')
cmd.action(
async (
fileArgument: string,
{ watch, port }: { watch?: boolean; port?: number },
) => {
const file = useGivenFileOrConfiguration(fileArgument)

let schema = (await loadOpenApiFile(file))
.specification as OpenAPI.Document

// watch file for changes
if (watch) {
fs.watchFile(file, async () => {
console.log(
kleur.bold().white('[INFO]'),
kleur.grey('Mock Server was updated.'),
)
schema = (
await loadOpenApiFile(file)
).resolveRefs() as OpenAPI.Document
})
}

console.log(kleur.bold().white('Available Paths'))
console.log()

if (
schema?.paths === undefined ||
Object.keys(schema?.paths).length === 0
) {
console.log(
kleur.bold().yellow('[WARN]'),
kleur.grey('Couldn’t find any paths in the OpenAPI file.'),
)
}

// loop through all paths
for (const path in schema?.paths ?? []) {
// loop through all methods
for (const method in schema.paths?.[path]) {
console.log(
`${kleur
.bold()
[getMethodColor(method)](
method.toUpperCase().padEnd(6),
)} ${kleur.grey(`${path}`)}`,
)
}
}

console.log()

const app = new Hono()

app.all('/*', (c) => {
const { method, path } = c.req

const operation = getOperationByMethodAndPath(schema, method, path)

console.log(
`${kleur
.bold()
[getMethodColor(method)](
method.toUpperCase().padEnd(6),
)} ${kleur.grey(`${path}`)}`,
`${kleur.grey('→')} ${
operation?.operationId
? kleur.white(operation.operationId)
: kleur.red('[ERROR] 404 Not Found')
}`,
)

if (!operation) {
return c.text('Not found', 404)
}

// if (!operation) {
// return c.text('Method not allowed', 405)
// }

const jsonResponseConfiguration =
operation.responses['200'].content['application/json']

const response = jsonResponseConfiguration.example
? jsonResponseConfiguration.example
: jsonResponseConfiguration.schema
? getExampleFromSchema(jsonResponseConfiguration.schema, {
emptyString: '…',
})
: null

return c.json(response)
})

serve(
{
fetch: app.fetch,
port: port ?? 3000,
},
(info) => {
console.log(
`${kleur.bold().green('➜ Mock Server')} ${kleur.white(
'listening on',
)} ${kleur.cyan(`http://localhost:${info.port}`)}`,
)
console.log()
},
)
},
)

return cmd
}
99 changes: 99 additions & 0 deletions packages/cli/src/commands/reference/ReferenceCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import fs from 'node:fs'
import { serve } from '@hono/node-server'
import { Command } from 'commander'
import { Hono } from 'hono'
import { stream } from 'hono/streaming'
import kleur from 'kleur'
import type { OpenAPI } from 'openapi-types'
import {
getHtmlDocument,
loadOpenApiFile,
useGivenFileOrConfiguration,
} from '../../utils'

export function ReferenceCommand() {
const cmd = new Command('reference')

cmd.description('Serve an API Reference from an OpenAPI file')
cmd.argument('[file]', 'OpenAPI file to show the reference for')
cmd.option('-w, --watch', 'watch the file for changes')
cmd.option(
'-p, --port <port>',
'set the HTTP port for the API reference server',
)
cmd.action(
async (
fileArgument: string,
{ watch, port }: { watch?: boolean; port?: number },
) => {
const file = useGivenFileOrConfiguration(fileArgument)

let specification = (await loadOpenApiFile(file))
.specification as OpenAPI.Document

if (
specification?.paths === undefined ||
Object.keys(specification?.paths).length === 0
) {
console.log(
kleur.bold().yellow('[WARN]'),
kleur.grey('Couldn’t find any paths in the OpenAPI file.'),
)
}

const app = new Hono()

app.get('/', (c) => {
return c.html(getHtmlDocument(specification, watch))
})

app.use('/__watcher', async (c, next) => {
c.header('Content-Type', 'text/event-stream')
c.header('Cache-Control', 'no-cache')
c.header('Connection', 'keep-alive')
await next()
})

app.get('/__watcher', (c) => {
return stream(c, async (stream) => {
// watch file for changes
if (watch) {
console.log(`Watch ${file}`)
fs.watchFile(file, async () => {
console.log(
kleur.bold().white('[INFO]'),
kleur.grey('OpenAPI file modified'),
)

specification = (await loadOpenApiFile(file))
.specification as OpenAPI.Document

stream.write('data: file modified\n\n')
})
}

while (true) {
await new Promise((resolve) => setTimeout(resolve, 100))
}
})
})

serve(
{
fetch: app.fetch,
port: port ?? 3000,
},
(info) => {
console.log(
`${kleur.bold().green('➜ API Reference Server')} ${kleur.white(
'listening on',
)} ${kleur.cyan(`http://localhost:${info.port}`)}`,
)
console.log()
},
)
},
)

return cmd
}
Loading

0 comments on commit 3554dc7

Please sign in to comment.