Skip to content

Commit 83d09f6

Browse files
committed
refactor: expose all prompts and CLI flags
1 parent b8f4001 commit 83d09f6

File tree

5 files changed

+184
-119
lines changed

5 files changed

+184
-119
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"dependencies": {
6363
"@adonisjs/ace": "^12.3.1-13",
6464
"@antfu/install-pkg": "^0.3.1",
65-
"edge.js": "^6.0.0-10",
65+
"edge.js": "^6.0.0",
6666
"gradient-string": "^2.0.2",
6767
"which-pm-runs": "^1.1.0"
6868
},

src/constants.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,7 @@ export const ADDITIONAL_PLUGINS = [
7676
PLUGINS.find((plugin) => plugin.name === '@japa/browser-client')!,
7777
]
7878

79-
export const PROJECT_TYPES = [{ name: 'TypeScript' as const }, { name: 'JavaScript' as const }]
79+
export const PROJECT_TYPES = [
80+
{ name: 'typescript' as const, message: 'TypeScript' },
81+
{ name: 'javascript' as const, message: 'JavaScript' },
82+
]

src/install_japa.ts

Lines changed: 159 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,77 @@ import gradient from 'gradient-string'
1414
import { fileURLToPath } from 'node:url'
1515
import detectPackageManager from 'which-pm-runs'
1616
import { installPackage } from '@antfu/install-pkg'
17-
import { basename, dirname, join, relative } from 'node:path'
1817
import { BaseCommand, args, flags } from '@adonisjs/ace'
1918
import { mkdir, readFile, writeFile } from 'node:fs/promises'
19+
import { basename, dirname, join, relative } from 'node:path'
2020

21-
import { ADDITIONAL_PLUGINS, ASSERTION_CHOICES, PROJECT_TYPES } from './constants.js'
2221
import type { PluginChoice } from './types.js'
22+
import { ADDITIONAL_PLUGINS, ASSERTION_CHOICES, PROJECT_TYPES } from './constants.js'
23+
24+
const TEMPLATES_DIR = join(dirname(fileURLToPath(import.meta.url)), '../templates')
2325

2426
export class InstallJapa extends BaseCommand {
27+
static #isPackageInstallFaked = false
2528
static commandName = 'create-japa'
26-
static description = 'Install Japa testing framework'
29+
static description = 'Configure Japa inside a fresh or an existing Node.js project'
30+
31+
/**
32+
* Fake package install for testing
33+
*/
34+
static fakePackageInstall() {
35+
this.#isPackageInstallFaked = true
36+
}
37+
38+
/**
39+
* Restore package install
40+
*/
41+
static restorePackageInstall() {
42+
this.#isPackageInstallFaked = false
43+
}
2744

2845
/**
2946
* Destination directory
3047
*/
31-
@args.string({ description: 'Destination', default: cwd() })
48+
@args.string({ description: 'Destination', default: 'process.cwd()' })
3249
declare destination: string
3350

3451
/**
3552
* Package manager to use
3653
*/
37-
@flags.string({ description: 'Force a package manager to be used', name: 'package-manager' })
54+
@flags.string({
55+
name: 'package-manager',
56+
description: 'Define the package manager to use for installing dependencies',
57+
})
3858
declare packageManager: string
3959

40-
static #isPackageInstallFaked = false
60+
/**
61+
* An array of plugins to configure
62+
*/
63+
@flags.array({
64+
description: 'Define a collection of plugins to install and configure',
65+
})
66+
declare plugins: string[]
67+
68+
/**
69+
* Project type
70+
*/
71+
@flags.string({
72+
description: 'Define the project type for which you want to configure Japa',
73+
})
74+
declare projectType: 'typescript' | 'javascript'
75+
76+
/**
77+
* Whether or not to create the sample test file
78+
*/
79+
@flags.boolean({
80+
description: 'Enable to create a sample test file',
81+
})
82+
declare sampleTestFile: boolean
4183

42-
#edge!: Edge
84+
/**
85+
* Edge is used for evaluating templates
86+
*/
87+
#edge = new Edge({ cache: false }).mount(TEMPLATES_DIR)
4388

4489
/**
4590
* Config file bindings
@@ -59,47 +104,11 @@ export class InstallJapa extends BaseCommand {
59104
*/
60105
#hasBrowserPlugin = false
61106

62-
/**
63-
* The project type. TypeScript or JavaScript
64-
*/
65-
#projectType!: 'TypeScript' | 'JavaScript'
66-
67-
/**
68-
* Selected assertion library
69-
*/
70-
#assertionLibrary!: PluginChoice
71-
72107
/**
73108
* Packages that should be installed
74109
*/
75110
#packageToInstall: string[] = ['@japa/runner']
76111

77-
/**
78-
* Fake package install for testing
79-
*/
80-
static fakePackageInstall() {
81-
this.#isPackageInstallFaked = true
82-
}
83-
84-
/**
85-
* Restore package install
86-
*/
87-
static restorePackageInstall() {
88-
this.#isPackageInstallFaked = false
89-
}
90-
91-
/**
92-
* Setup edge and detect package manager
93-
*/
94-
#setup() {
95-
if (!this.packageManager) {
96-
this.packageManager = detectPackageManager()?.name || 'npm'
97-
}
98-
99-
const templatesDir = join(dirname(fileURLToPath(import.meta.url)), '../templates')
100-
this.#edge = new Edge({ cache: false }).mount(templatesDir)
101-
}
102-
103112
/**
104113
* Print Title in Ascii art
105114
*/
@@ -114,72 +123,29 @@ export class InstallJapa extends BaseCommand {
114123
this.logger.log('')
115124
}
116125

117-
/**
118-
* Given selections and list of choices, this method
119-
* returns the original choices object for the selections
120-
*/
121-
#findSelectionByName<Choices extends Record<string, any>[], Selections extends string | string[]>(
122-
choices: Choices,
123-
selections: Selections
124-
): Selections extends string ? Choices[number] : Choices[number][] {
125-
if (Array.isArray(selections)) {
126-
return selections.map((selection) => choices.find((r) => r.name === selection)!) as any
127-
}
128-
129-
return choices.find((r) => r.name === selections) as any
130-
}
131-
132126
/**
133127
* Prompt to select an assertion library. Assertion library
134128
* is optional
135129
*/
136130
async #promptAssertionLibrary() {
137-
this.#assertionLibrary = await this.prompt.choice(
131+
const assertionPlugin = await this.prompt.choice(
138132
'Select the assertion library',
139133
ASSERTION_CHOICES,
140-
{ result: (selection) => this.#findSelectionByName(ASSERTION_CHOICES, selection) }
134+
{
135+
validate: (value) => !!value,
136+
}
141137
)
142-
143-
this.#prepareAssertionLibraryConfig(this.#assertionLibrary)
144-
this.#packageToInstall.push(...(this.#assertionLibrary.packagesToInstall || []))
138+
this.plugins.push(assertionPlugin)
145139
}
146140

147141
/**
148142
* Prompt to select additional plugins
149143
*/
150144
async #promptAdditionalPlugins() {
151145
const plugins = await this.prompt.multiple('Select additional plugins', ADDITIONAL_PLUGINS, {
152-
result: (selections) => this.#findSelectionByName(ADDITIONAL_PLUGINS, selections),
153146
default: [],
154147
})
155-
156-
this.#preparePluginsConfig(plugins)
157-
this.#hasBrowserPlugin = this.#hasPickedPlugin(plugins, '@japa/browser-client')
158-
this.#packageToInstall.push(...plugins.map((plugin) => plugin.packagesToInstall || []).flat())
159-
160-
return plugins
161-
}
162-
163-
/**
164-
* Ask if the project a JS or TS project
165-
*/
166-
async #promptProjectType() {
167-
this.#projectType = await this.prompt.choice('Select the project type', PROJECT_TYPES)
168-
return this.#projectType
169-
}
170-
171-
/**
172-
* Should we create a sample test file?
173-
*/
174-
#promptSampleTestFile() {
175-
return this.prompt.confirm('Want us to create a sample test?')
176-
}
177-
178-
/**
179-
* Check if given plugin has been selected by the user
180-
*/
181-
#hasPickedPlugin(plugins: PluginChoice[], name: string) {
182-
return !!plugins.find((plugin) => plugin.name === name)
148+
this.plugins = this.plugins.concat(plugins)
183149
}
184150

185151
/**
@@ -204,13 +170,40 @@ export class InstallJapa extends BaseCommand {
204170
}
205171
}
206172

173+
/**
174+
* Processing selected plugins
175+
*/
176+
#processSelectedPlugins() {
177+
/**
178+
* Configuring assertion library
179+
*/
180+
const assertionPlugin = ASSERTION_CHOICES.find((choice) => this.plugins.includes(choice.name))
181+
if (assertionPlugin) {
182+
this.#prepareAssertionLibraryConfig(assertionPlugin)
183+
this.#packageToInstall.push(...(assertionPlugin.packagesToInstall || []))
184+
}
185+
186+
/**
187+
* Configuring rest of the plugins
188+
*/
189+
const additionalPlugins = ADDITIONAL_PLUGINS.filter((choice) =>
190+
this.plugins.includes(choice.name)
191+
)
192+
this.#preparePluginsConfig(additionalPlugins)
193+
this.#hasBrowserPlugin = this.plugins.includes('@japa/browser-client')
194+
this.#packageToInstall.push(
195+
...additionalPlugins.map((plugin) => plugin.packagesToInstall || []).flat()
196+
)
197+
}
198+
207199
/**
208200
* Create sample test files.
209201
* May also create a browser test file if browser plugin is selected
210202
*/
211203
async #createSampleTestFiles() {
204+
const assertionPlugin = ASSERTION_CHOICES.find((choice) => this.plugins.includes(choice.name))
212205
const content = await this.#edge.render('test-sample', {
213-
assertPlugin: this.#assertionLibrary.namedImport,
206+
assertPlugin: assertionPlugin?.namedImport,
214207
})
215208

216209
const destination = this.#hasBrowserPlugin ? `tests/unit/maths.spec` : 'tests/maths.spec'
@@ -222,8 +215,12 @@ export class InstallJapa extends BaseCommand {
222215
}
223216
}
224217

218+
/**
219+
* Returns the file extensions based upon the selected
220+
* project type
221+
*/
225222
#getExtension() {
226-
return this.#projectType === 'TypeScript' ? 'ts' : 'js'
223+
return this.projectType === 'typescript' ? 'ts' : 'js'
227224
}
228225

229226
/**
@@ -282,7 +279,7 @@ export class InstallJapa extends BaseCommand {
282279
const pkgJsonPath = join(this.destination, 'package.json')
283280

284281
const testScript =
285-
this.#projectType === 'TypeScript'
282+
this.projectType === 'typescript'
286283
? 'node --loader ts-node/esm --enable-source-maps bin/test.ts'
287284
: 'node bin/test.js'
288285

@@ -329,17 +326,76 @@ export class InstallJapa extends BaseCommand {
329326
sticker.render()
330327
}
331328

332-
async run() {
333-
this.#setup()
329+
/**
330+
* The prepare lifecycle hook to setup the initial state
331+
*/
332+
async prepare() {
334333
this.#printTitle()
334+
if (!this.packageManager) {
335+
this.packageManager = detectPackageManager()?.name || 'npm'
336+
}
337+
if (!this.destination) {
338+
this.destination = cwd()
339+
}
340+
}
341+
342+
/**
343+
* The interact lifecycle hook to trigger prompts
344+
*/
345+
async interact() {
346+
if (!this.projectType) {
347+
this.projectType = await this.prompt.choice('Select the project type', PROJECT_TYPES, {
348+
validate: (value) => !!value,
349+
})
350+
}
351+
352+
if (!this.plugins) {
353+
this.plugins = []
354+
await this.#promptAssertionLibrary()
355+
await this.#promptAdditionalPlugins()
356+
}
357+
358+
if (this.sampleTestFile === undefined) {
359+
this.sampleTestFile = await this.prompt.confirm('Want us to create a sample test?')
360+
}
361+
}
362+
363+
/**
364+
* The run method is invoked by ace after the prepare
365+
* and the interact methods
366+
*/
367+
async run() {
368+
await this.prepare()
369+
await this.interact()
370+
371+
/**
372+
* If no plugins were selected
373+
*/
374+
if (!this.plugins) {
375+
this.plugins = []
376+
}
335377

336378
/**
337-
* Prompt user for selections
379+
* Ensure the project type was defined
338380
*/
339-
await this.#promptAssertionLibrary()
340-
await this.#promptAdditionalPlugins()
341-
await this.#promptProjectType()
342-
const createSampleTest = await this.#promptSampleTestFile()
381+
if (!this.projectType) {
382+
this.exitCode = 1
383+
this.logger.error(
384+
'Missing project type. Make sure to define it using the "--project-type" flag'
385+
)
386+
return
387+
}
388+
389+
/**
390+
* Invalid mentioned project type
391+
*/
392+
if (!PROJECT_TYPES.find(({ name }) => this.projectType === name)) {
393+
this.exitCode = 1
394+
this.logger.error('Invalid project type. It must be either "javascript" or "typescript"')
395+
return
396+
}
397+
398+
this.#processSelectedPlugins()
343399

344400
/**
345401
* Create the japa configuration file
@@ -355,7 +411,7 @@ export class InstallJapa extends BaseCommand {
355411
/**
356412
* Create the sample test files
357413
*/
358-
if (createSampleTest) {
414+
if (this.sampleTestFile) {
359415
await this.#createSampleTestFiles()
360416
}
361417

0 commit comments

Comments
 (0)