This repository has been archived by the owner on Mar 19, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move all commands to separate files
- Loading branch information
Showing
9 changed files
with
4,746 additions
and
4,700 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.