groq-cli
Advanced tools
Comparing version 0.1.0 to 0.2.0
{ | ||
"name": "groq-cli", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Run GROQ in the command line", | ||
@@ -33,8 +33,10 @@ "license": "MIT", | ||
"chalk": "^2.4.2", | ||
"get-it": "^5.0.0", | ||
"get-stdin": "^7.0.0", | ||
"groq": "0.0.9", | ||
"groq-js": "0.0.1", | ||
"groq-js": "0.1.3", | ||
"json-lexer": "^1.1.1", | ||
"meow": "^5.0.0" | ||
"meow": "^5.0.0", | ||
"ndjson": "^1.5.0", | ||
"regenerator-runtime": "^0.13.3", | ||
"stream-to-async-iterator": "^0.2.0" | ||
}, | ||
@@ -41,0 +43,0 @@ "devDependencies": { |
194
src/cli.js
#!/usr/bin/env node | ||
/* eslint-disable id-length, no-process-exit */ | ||
require("regenerator-runtime/runtime") | ||
const meow = require('meow') | ||
const readFileStream = require('./readFileStream') | ||
const { parse, evaluate } = require('groq-js') | ||
const getStdin = require('get-stdin') | ||
const chalk = require('chalk') | ||
const ndjson = require('ndjson') | ||
require('./gracefulQuit') | ||
const colorizeJson = require('./colorizeJson') | ||
const fromUrl = require('./fromUrl') | ||
const S2A = require('stream-to-async-iterator').default | ||
const cli = meow( | ||
@@ -19,41 +20,50 @@ ` | ||
Options | ||
${chalk.green(`--file ./path/to/file`)} | ||
${chalk.green(`--url https://aniftyapi.dev/endpoint`)} | ||
${chalk.green( | ||
`--primary results | The primary element with the array you want to query` | ||
)} | ||
${chalk.green(`--pretty colorized JSON output [Default: false]`)} | ||
${chalk.green(`-i, --input One of: ndjson, json, null`)} | ||
${chalk.green(`-o, --output One of: ndjson, json, pretty`)} | ||
${chalk.green(`-p, --pretty Shortcut for --output=pretty`)} | ||
${chalk.green(`-n, --ndjson Shortcut for --input=ndjson --output=ndjson`)} | ||
Input formats | ||
${chalk.green(`json`)} Reads a JSON object from stdin. | ||
${chalk.green(`ndjson`)} Reads a JSON stream from stdin. | ||
${chalk.green(`null`)} Reads nothing. | ||
Output formats | ||
${chalk.green(`json`)} Formats the output as JSON. | ||
${chalk.green(`pretty`)} Formats the output as pretty JSON. | ||
${chalk.green(`ndjson`)} Streams the result as NDJSON. | ||
Examples | ||
${chalk.grey(`# Query data in a ndjson-file`)} | ||
${chalk.green(`$ groq '*[_type == "post"]{title}' --file ./blog.ndjson`)} | ||
${chalk.grey(`# Query data in a file`)} | ||
${chalk.green(`$ cat blog.json | groq 'count(posts)' `)} | ||
${chalk.grey(`# Query data in a NDJSON file`)} | ||
${chalk.green(`$ cat blog.ndjson | groq --input ndjson '*[_type == "post"]{title}' `)} | ||
${chalk.grey(`# Query JSON data from an URL`)} | ||
${chalk.green( | ||
`$ groq '*[completed == false]{title}' --url https://jsonplaceholder.typicode.com/todos` | ||
`$ curl -s https://jsonplaceholder.typicode.com/todos | groq --pretty '*[completed == false]{title}'` | ||
)} | ||
${chalk.grey(`# Query data from stdIn`)} | ||
${chalk.green( | ||
`$ curl -s https://jsonplaceholder.typicode.com/todos | groq "*[completed == false]{'mainTitle': title, ...}" --pretty` | ||
)} | ||
`, | ||
{ | ||
flags: { | ||
file: { | ||
type: 'string', | ||
default: undefined | ||
pretty: { | ||
type: 'boolean', | ||
alias: 'p', | ||
default: false | ||
}, | ||
primary: { | ||
ndjson: { | ||
type: 'boolean', | ||
alias: 'n', | ||
default: false | ||
}, | ||
input: { | ||
type: 'string', | ||
default: undefined | ||
alias: 'i', | ||
default: 'json' | ||
}, | ||
url: { | ||
output: { | ||
type: 'string', | ||
default: undefined | ||
}, | ||
pretty: { | ||
type: 'boolean', | ||
default: false | ||
alias: 'o', | ||
default: 'json' | ||
} | ||
@@ -69,23 +79,9 @@ } | ||
function parseDocuments (data, primary) { | ||
if (primary && !JSON.parse(data)[primary]) { | ||
throw new Error(`Is the primary key correct?\n\n${primary}`) | ||
function validateChoice(title, input, choices) { | ||
if (!choices.includes(input)) { | ||
throw Error(chalk.yellow(`Unknown ${title}: ${input}. Valid choices are: ${choices.join(', ')}.`)) | ||
} | ||
try { | ||
return primary ? JSON.parse(data)[primary] : JSON.parse(data) | ||
} catch (err) { | ||
try { | ||
return data | ||
.toString() | ||
.trim() | ||
.split('\n') | ||
.map(JSON.parse) | ||
} catch (error) { | ||
throw new Error(`Is the input valid JSON/NDJSON?\n\n${error}`) | ||
} | ||
} | ||
} | ||
function check ({ query, file, url, stdIn }) { | ||
function check ({ query, inputFormat, outputFormat }) { | ||
if (!query) { | ||
@@ -98,41 +94,91 @@ throw Error( | ||
} | ||
if (!file && !url && !stdIn) { | ||
throw Error( | ||
chalk.yellow( | ||
'There’s no data to query. To learn more, run\n\n $ groq --help' | ||
) | ||
) | ||
} | ||
validateChoice("input format", inputFormat, ['json', 'ndjson', 'null']) | ||
validateChoice("output format", outputFormat, ['json', 'ndjson', 'pretty']) | ||
return true | ||
} | ||
async function parseQuery () { | ||
async function* outputJSON(result) { | ||
yield JSON.stringify(await result.get()) | ||
yield "\n" | ||
} | ||
async function* outputPrettyJSON(result) { | ||
yield colorizeJson(await result.get()) | ||
yield "\n" | ||
} | ||
async function* outputNDJSON(result) { | ||
if (result.getType() == 'array') { | ||
for await (const value of result) { | ||
yield JSON.stringify(await value.get()) | ||
yield "\n" | ||
} | ||
} else { | ||
yield JSON.stringify(await result.get()) | ||
yield "\n" | ||
} | ||
} | ||
const OUTPUTTERS = { | ||
json: outputJSON, | ||
pretty: outputPrettyJSON, | ||
ndjson: outputNDJSON, | ||
} | ||
async function inputJSON() { | ||
const dataset = JSON.parse(await getStdin()) | ||
return {dataset, root: dataset} | ||
} | ||
function inputNDJSON() { | ||
const dataset = new S2A(process.stdin.pipe(ndjson())) | ||
return {dataset} | ||
} | ||
const INPUTTERS = { | ||
json: inputJSON, | ||
ndjson: inputNDJSON, | ||
} | ||
async function* runQuery() { | ||
const { flags, input } = cli | ||
const { file, url, primary, pretty } = flags | ||
const { pretty, ndjson: isNdjson } = flags | ||
let { input: inputFormat, output: outputFormat } = flags | ||
const query = input[0] | ||
const stdIn = await getStdin() | ||
check({ query, file, url, stdIn }) | ||
let documents = [] | ||
if (file) { | ||
const fileContent = await readFileStream(file).catch(handleError) | ||
documents = await parseDocuments(fileContent, primary) | ||
} else if (url) { | ||
const urlContent = await fromUrl(url) | ||
documents = await parseDocuments(urlContent, primary) | ||
} else if (stdIn) { | ||
documents = await parseDocuments(stdIn, primary) | ||
if (pretty) { | ||
outputFormat = 'pretty' | ||
} | ||
if (isNdjson) { | ||
outputFormat = 'ndjson' | ||
inputFormat = 'ndjson' | ||
} | ||
check({ query, inputFormat, outputFormat }) | ||
// Parse query | ||
const tree = parse(query) | ||
const result = await evaluate(tree, { documents }).get() | ||
if (pretty) { | ||
return colorizeJson(result) | ||
} | ||
// Read input | ||
const inputter = INPUTTERS[inputFormat] | ||
const options = await inputter() | ||
return JSON.stringify(result) | ||
// Execute query | ||
const result = await evaluate(tree, options) | ||
// Stream output | ||
const streamer = OUTPUTTERS[outputFormat] | ||
yield* await streamer(result) | ||
} | ||
parseQuery() | ||
.then(result => console.log(result)) | ||
.catch(handleError) | ||
async function main() { | ||
for await (const data of runQuery()) { | ||
process.stdout.write(data) | ||
} | ||
} | ||
main().catch(handleError) |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
10245
210
72
9
8
1
+ Addedndjson@^1.5.0
+ Addedregenerator-runtime@^0.13.3
+ Addedgroq-js@0.1.3(transitive)
+ Addedjson-stringify-safe@5.0.1(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedndjson@1.5.0(transitive)
+ Addedregenerator-runtime@0.13.11(transitive)
+ Addedsplit2@2.2.0(transitive)
+ Addedstream-to-async-iterator@0.2.0(transitive)
- Removedget-it@^5.0.0
- Removed@sanity/timed-out@4.0.2(transitive)
- Removedcapture-stack-trace@1.0.2(transitive)
- Removedcreate-error-class@3.0.2(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddecompress-response@3.3.0(transitive)
- Removedfollow-redirects@1.15.6(transitive)
- Removedform-urlencoded@2.0.9(transitive)
- Removedfrom2@2.3.0(transitive)
- Removedget-it@5.2.1(transitive)
- Removedgroq-js@0.0.1(transitive)
- Removedinto-stream@3.1.0(transitive)
- Removedis-plain-object@2.0.4(transitive)
- Removedis-retry-allowed@1.2.0(transitive)
- Removedis-stream@1.1.0(transitive)
- Removedisobject@3.0.1(transitive)
- Removedmimic-response@1.0.1(transitive)
- Removedms@2.0.0(transitive)
- Removednano-pubsub@1.0.2(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedp-is-promise@1.1.0(transitive)
- Removedparse-headers@2.0.5(transitive)
- Removedprogress-stream@2.0.0(transitive)
- Removedquerystringify@2.2.0(transitive)
- Removedrequires-port@1.0.0(transitive)
- Removedsame-origin@0.1.1(transitive)
- Removedsimple-concat@1.0.1(transitive)
- Removedspeedometer@1.0.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedurl-parse@1.5.10(transitive)
Updatedgroq-js@0.1.3