Skip to content

Commit da95c8b

Browse files
committed
feat(module,task): add --json to module search and task list
1 parent a3fa129 commit da95c8b

6 files changed

Lines changed: 149 additions & 6 deletions

File tree

‎packages/nuxt-cli/src/commands/_shared.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,10 @@ export const rootDirArgs = {
6262
default: undefined,
6363
},
6464
}asconstsatisfiesRecord<string,ArgDef>
65+
66+
exportconstjsonArgs={
67+
json: {
68+
type: 'boolean',
69+
description: 'Print output as JSON',
70+
},
71+
}asconstsatisfiesRecord<string,ArgDef>

‎packages/nuxt-cli/src/commands/module/search.ts‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import { defineCommand } from 'citty'
66
importfuzzysortfrom'fuzzysort'
77
import{kebabCase,upperFirst}from'scule'
88

9+
import{withDirectStdout}from'../../utils/console'
910
import{formatInfoBox}from'../../utils/formatting'
1011
import{logger}from'../../utils/logger'
1112
import{logNetworkError}from'../../utils/network'
1213
import{DEFAULT_NUXT_VERSION,getNuxtVersion}from'../../utils/versions'
13-
import{cwdArgs}from'../_shared'
14+
import{cwdArgs,jsonArgs}from'../_shared'
1415
import{checkNuxtCompatibility,fetchModules,MODULES_API_URL}from'./_utils'
1516

1617
constDASH_RE=/-/g
@@ -48,12 +49,13 @@ export default defineCommand({
4849
required: false,
4950
valueHint: '2|3',
5051
},
52+
...jsonArgs,
5153
},
5254
asyncsetup(ctx){
5355
constnuxtVersion=ctx.args.nuxtVersion
5456
? normalizeNuxtVersion(ctx.args.nuxtVersion)
5557
: awaitgetNuxtVersion(ctx.args.cwd).catch(()=>DEFAULT_NUXT_VERSION)
56-
returnfindModuleByKeywords(ctx.args._.join(' '),nuxtVersion)
58+
returnfindModuleByKeywords(ctx.args._.join(' '),nuxtVersion,ctx.args.json)
5759
},
5860
})
5961

@@ -65,7 +67,7 @@ export function normalizeNuxtVersion(version: string): string {
6567
: version
6668
}
6769

68-
asyncfunctionfindModuleByKeywords(query: string,nuxtVersion: string){
70+
asyncfunctionfindModuleByKeywords(query: string,nuxtVersion: string,json?: boolean){
6971
constallModules=awaitfetchModules().catch((err)=>{
7072
logNetworkError(err,{url: MODULES_API_URL})
7173
process.exit(1)
@@ -86,11 +88,33 @@ async function findModuleByKeywords(query: string, nuxtVersion: string) {
8688
].filter(Boolean).join(' ')),
8789
}))
8890

89-
constresults=fuzzysort.go(query,targets,{
91+
constmatches=fuzzysort.go(query,targets,{
9092
keys: ['name','npm','rest'],
9193
threshold: SCORE_THRESHOLD,
9294
limit: RESULT_LIMIT,
93-
}).map(({obj: { item }})=>{
95+
}).map(({obj: { item }})=>item)
96+
97+
if(json){
98+
constpayload=JSON.stringify({
99+
query,
100+
nuxtVersion,
101+
modules: matches.map(item=>({
102+
name: item.name,
103+
package: item.npm,
104+
description: item.description,
105+
homepage: item.website,
106+
repository: item.github,
107+
compatibility: item.compatibility?.nuxt||'*',
108+
stars: item.stats.stars,
109+
monthlyDownloads: item.stats.downloads,
110+
install: `npx nuxt add ${item.name}`,
111+
})),
112+
},null,2)
113+
awaitwithDirectStdout(()=>process.stdout.write(`${payload}\n`))
114+
return
115+
}
116+
117+
constresults=matches.map((item)=>{
94118
constres: Record<string,string>={
95119
name: item.name,
96120
package: item.npm,

‎packages/nuxt-cli/src/commands/task/list.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import { styleText } from 'node:util'
55

66
import{defineCommand}from'citty'
77

8+
import{withDirectStdout}from'../../utils/console'
89
import{logger}from'../../utils/logger'
910
import{resolveRootDir}from'../../utils/paths'
10-
import{rootDirArgs}from'../_shared'
11+
import{jsonArgs,rootDirArgs}from'../_shared'
1112
import{emptyTaskListHint,fetchTasks,missingTaskRoutesHint,reportTaskError,resolveTaskServer,taskArgs}from'./_utils'
1213

1314
exportdefaultdefineCommand({
@@ -18,6 +19,7 @@ export default defineCommand({
1819
args: {
1920
...rootDirArgs,
2021
...taskArgs,
22+
...jsonArgs,
2123
},
2224
asyncrun(ctx){
2325
constcwd=resolveRootDir(ctx.args)
@@ -35,6 +37,15 @@ export default defineCommand({
3537
const{ tasks ={}, scheduledTasks }=(response.data||{})asTaskList
3638
constnames=Object.keys(tasks).sort()
3739

40+
if(ctx.args.json){
41+
constpayload=JSON.stringify({
42+
tasks: names.map(name=>({ name,description: tasks[name]?.description||null})),
43+
scheduledTasks: (scheduledTasks||[]).map(({ cron, tasks })=>({ cron, tasks })),
44+
},null,2)
45+
awaitwithDirectStdout(()=>process.stdout.write(`${payload}\n`))
46+
return
47+
}
48+
3849
if(names.length===0){
3950
logger.info(`No tasks found. ${awaitemptyTaskListHint(cwd)}`)
4051
return
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
importprocessfrom'node:process'
2+
3+
import{runCommand}from'citty'
4+
import{afterEach,beforeEach,describe,expect,it,vi}from'vitest'
5+
6+
importsearchfrom'../../../../src/commands/module/search'
7+
8+
const{ fetchModules }=vi.hoisted(()=>({fetchModules: vi.fn()}))
9+
10+
vi.mock('../../../../src/commands/module/_utils',async(importOriginal)=>{
11+
constoriginal=awaitimportOriginal<typeofimport('../../../../src/commands/module/_utils')>()
12+
return{ ...original,fetchModules}
13+
})
14+
15+
functionmoduleEntry(name: string){
16+
return{
17+
name,
18+
npm: `nuxt-${name}`,
19+
repo: `nuxt-modules/${name}`,
20+
github: `https://github.com/nuxt-modules/${name}`,
21+
website: `https://${name}.nuxtjs.org`,
22+
description: `The ${name} module`,
23+
category: 'Devtools',
24+
tags: [name],
25+
compatibility: {nuxt: '^3.0.0 || ^4.0.0'},
26+
maintainers: [{name: 'someone',github: 'someone'}],
27+
stats: {stars: 1234,downloads: 56_789},
28+
}
29+
}
30+
31+
letstdout: string
32+
33+
describe('module search',()=>{
34+
beforeEach(()=>{
35+
stdout=''
36+
fetchModules.mockResolvedValue([moduleEntry('image'),moduleEntry('content')])
37+
vi.spyOn(process.stdout,'write').mockImplementation((chunk: any)=>{
38+
stdout+=String(chunk)
39+
returntrue
40+
})
41+
})
42+
43+
afterEach(()=>{
44+
vi.restoreAllMocks()
45+
})
46+
47+
it('prints machine readable results with `--json`',async()=>{
48+
awaitrunCommand(search,{rawArgs: ['image','--json','--nuxtVersion','4.0.0']})
49+
50+
expect(JSON.parse(stdout)).toEqual({
51+
query: 'image',
52+
nuxtVersion: '4.0.0',
53+
modules: [{
54+
name: 'image',
55+
package: 'nuxt-image',
56+
description: 'The image module',
57+
homepage: 'https://image.nuxtjs.org',
58+
repository: 'https://github.com/nuxt-modules/image',
59+
compatibility: '^3.0.0 || ^4.0.0',
60+
stars: 1234,
61+
monthlyDownloads: 56_789,
62+
install: 'npx nuxt add image',
63+
}],
64+
})
65+
})
66+
67+
it('prints an empty module list with `--json` when nothing matches',async()=>{
68+
awaitrunCommand(search,{rawArgs: ['zzzzzz','--json','--nuxtVersion','4.0.0']})
69+
70+
expect(JSON.parse(stdout)).toEqual({query: 'zzzzzz',nuxtVersion: '4.0.0',modules: []})
71+
})
72+
73+
it('leaves the human output untouched without `--json`',async()=>{
74+
awaitrunCommand(search,{rawArgs: ['image','--nuxtVersion','4.0.0']})
75+
76+
expect(stdout).toContain('image')
77+
expect(()=>JSON.parse(stdout)).toThrow()
78+
})
79+
})

‎packages/nuxt-cli/test/unit/commands/task.spec.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@ describe('task list', () => {
156156
expect(stdout).toContain('0 * * * *')
157157
})
158158

159+
it('prints machine readable output with `--json`',async()=>{
160+
constcode=awaitrunTaskCommand(list,['--url',origin,'--json'])
161+
162+
expect(code).toBe(0)
163+
expect(JSON.parse(stdout)).toEqual({
164+
tasks: [
165+
{name: 'db:migrate',description: 'Migrate the database'},
166+
{name: 'db:seed',description: null},
167+
],
168+
scheduledTasks: [{cron: '0 * * * *',tasks: ['db:seed']}],
169+
})
170+
})
171+
172+
it('prints an empty payload with `--json` when the server exposes no tasks',async()=>{
173+
taskList={tasks: {}}
174+
constcode=awaitrunTaskCommand(list,['--url',origin,`--cwd=${cwd}`,'--json'])
175+
176+
expect(code).toBe(0)
177+
expect(JSON.parse(stdout)).toEqual({tasks: [],scheduledTasks: []})
178+
})
179+
159180
it('finds the dev server from the lock file',async()=>{
160181
awaitwriteLock(origin)
161182
constcode=awaitrunTaskCommand(list,[`--cwd=${cwd}`])

‎packages/nuxt-cli/test/unit/help.spec.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ describe('help', () => {
491491
492492
--cwd=<directory> Specify the root directory of your Nuxt project (Default: .)
493493
--nuxtVersion=<2|3> Filter by Nuxt version and list compatible modules only (auto detected by default)
494+
--json Print output as JSON
494495
"
495496
`)
496497
})

0 commit comments

Comments
 (0)