@spark-ui/cli-utils 2.14.0 → 2.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.15.1](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.15.0...@spark-ui/cli-utils@2.15.1) (2024-07-03)
7
+
8
+ **Note:** Version bump only for package @spark-ui/cli-utils
9
+
10
+ # [2.15.0](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.14.0...@spark-ui/cli-utils@2.15.0) (2024-07-03)
11
+
12
+ ### Bug Fixes
13
+
14
+ - **cli-utils:** add some feedback when not loading a given config file ([1231fe6](https://github.com/adevinta/spark/commit/1231fe6cb94c15643a11278c3210836e81059364))
15
+ - **cli-utils:** fix config merge method to allow proper option override ([8b6a124](https://github.com/adevinta/spark/commit/8b6a124c6e70f05769305bf6ca84137c7a05510c))
16
+ - **cli-utils:** fix logger issue on generate script after changes ([083c9b0](https://github.com/adevinta/spark/commit/083c9b043d1837a807b159a8143fd16bf8d7087e))
17
+ - **cli-utils:** merge values ([a7e8129](https://github.com/adevinta/spark/commit/a7e81292d452d4ee026f3d4705198ac4ecc6363e))
18
+ - **cli-utils:** merging configs ([aeb6251](https://github.com/adevinta/spark/commit/aeb6251d23bc506674e4545ebad928081292f520))
19
+ - **cli-utils:** missalignment on the default config values nesting ([79729f7](https://github.com/adevinta/spark/commit/79729f722f4ecf6f0c671e2c1c1d5929a87d98c9))
20
+ - **cli-utils:** output file creates new content ([973ffa8](https://github.com/adevinta/spark/commit/973ffa85e6987dbd87a119e9253db57556a705d4))
21
+ - **cli-utils:** remove betta tagging ([96cafae](https://github.com/adevinta/spark/commit/96cafae45e4a4759ef9ff4a763963f26cb898cb6))
22
+ - **cli-utils:** remove extra logs ([f588649](https://github.com/adevinta/spark/commit/f588649eb299fc42ef9d6dbf8f8f4eebd0cfd6a9))
23
+ - **cli-utils:** remove unnecesary log ([614d834](https://github.com/adevinta/spark/commit/614d834d5d7a0f3084b3097e2dee5436b369fbb6))
24
+ - log config setted ([190a058](https://github.com/adevinta/spark/commit/190a0581ab6219591c5d3a3ecde2ee46d504d7ca))
25
+ - priority of commands ([ed178a5](https://github.com/adevinta/spark/commit/ed178a51a8cea0255c96467b0d8760daf8f851dd))
26
+
27
+ ### Features
28
+
29
+ - **cli-utils:** exoposes all configuration parameters as a build argument ([3e88a99](https://github.com/adevinta/spark/commit/3e88a998c05da026cd196b3dd54a2ad50506266c))
30
+ - mixing parameters ([da1bf1d](https://github.com/adevinta/spark/commit/da1bf1d838df2c577755eb3c651cd0bba8d07203))
31
+
6
32
  # [2.14.0](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.13.8...@spark-ui/cli-utils@2.14.0) (2024-06-14)
7
33
 
8
34
  ### Features
@@ -6,7 +6,7 @@ import { TemplateGenerator } from '../src/generate/generators/index.mjs'
6
6
  import { Logger, System } from '../src/core/index.mjs'
7
7
  import { DescriptionValidator, NameValidator } from '../src/generate/validators/index.mjs'
8
8
 
9
- const logger = new Logger()
9
+ const logger = new Logger({ verbose: true })
10
10
  const system = new System({ logger })
11
11
  const generator = new TemplateGenerator({ system })
12
12
 
@@ -1,15 +1,21 @@
1
1
  #! /usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander'
4
- import { adoption } from '../src/scan/index.mjs'
4
+ import { adoption, config } from '../src/scan/index.mjs'
5
5
 
6
6
  const program = new Command()
7
7
 
8
8
  program
9
9
  .command('adoption')
10
10
  .description('Scan @spark-ui adoption for .tsx files with given imports')
11
- .option('-c, --configuration <path>', 'configuration file route', '.spark-ui.cjs')
12
- .option('-o, --output <path>', 'output file route')
11
+ .option('-c, --configuration <config>', 'configuration file route')
12
+ .option('-o, --output <output>', 'output file route')
13
+ .option('-v, --verbose', 'output log information', false)
14
+ .option('-d, --details', 'output information about each match', config.details)
15
+ .option('-s, --sort <sort>', 'sort results (count or alphabetical)', config.sort)
16
+ .option('-dir, --directory <directory>', 'directory to parse', config.directory)
17
+ .option('-ext, --extensions <extensions...>', 'file extensions to parse', config.extensions)
18
+ .option('-i, --imports <imports...>', 'import patterns to identify', config.imports)
13
19
  .action(adoption)
14
20
 
15
21
  program.parse(process.argv)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spark-ui/cli-utils",
3
- "version": "2.14.0",
3
+ "version": "2.15.1",
4
4
  "description": "Spark CLI utils",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -45,5 +45,5 @@
45
45
  "devDependencies": {
46
46
  "@types/fs-extra": "11.0.4"
47
47
  },
48
- "gitHead": "70cbe1732252c1a1eac56af3d212fe2d33e4dcdc"
48
+ "gitHead": "89a5654b5e5b02d911e40ead40c608617b5bd44b"
49
49
  }
@@ -1,20 +1,50 @@
1
- /* eslint-disable no-console */
2
1
  import chalk from 'chalk'
3
2
 
4
3
  export class Logger {
5
- success(message) {
6
- console.log(chalk.green(message))
4
+ #force = false
5
+
6
+ constructor({ verbose } = {}) {
7
+ this.verbose = verbose
8
+
9
+ if (typeof Logger.instance === 'object') {
10
+ return Logger.instance
11
+ }
12
+
13
+ Logger.instance = this
14
+
15
+ return this
16
+ }
17
+
18
+ #log({ type = v => v, force, verbose }, ...args) {
19
+ if (force || verbose) {
20
+ console.log(type(...args)) // eslint-disable-line no-console
21
+ }
22
+ this.#force = false
23
+ }
24
+
25
+ force() {
26
+ this.#force = true
27
+
28
+ return this
29
+ }
30
+
31
+ error(...args) {
32
+ this.#log({ type: chalk.red, force: this.#force, verbose: this.verbose }, ...args)
33
+ }
34
+
35
+ warning(...args) {
36
+ this.#log({ type: chalk.yellow, force: this.#force, verbose: this.verbose }, ...args)
7
37
  }
8
38
 
9
- error(message) {
10
- console.log(chalk.red(message))
39
+ info(...args) {
40
+ this.#log({ type: chalk.cyan, force: this.#force, verbose: this.verbose }, ...args)
11
41
  }
12
42
 
13
- info(message) {
14
- console.log(chalk.yellow(message))
43
+ success(...args) {
44
+ this.#log({ type: chalk.green, force: this.#force, verbose: this.verbose }, ...args)
15
45
  }
16
46
 
17
- warning(message) {
18
- console.log(chalk.green(message))
47
+ break() {
48
+ this.#log({ type: chalk.green, force: this.#force, verbose: this.verbose }, '')
19
49
  }
20
50
  }
@@ -0,0 +1,37 @@
1
+ module.exports = {
2
+ adoption: {
3
+ /**
4
+ * Shows (or not) details about each package adoption
5
+ * @param {boolean}
6
+ * @default false
7
+ */
8
+ details: false,
9
+ /**
10
+ * Sorts packages list, based on count or alphabetical order
11
+ * @param {'count'|'alphabetical'}
12
+ * @default 'count'
13
+ */
14
+ sort: 'count',
15
+ /**
16
+ * Packages pattern to be scanned
17
+ * @params {string[]}
18
+ */
19
+ imports: ['@spark-ui'],
20
+ /**
21
+ * File extensions to be scanned
22
+ * @params {string[]}
23
+ */
24
+ extensions: ['.tsx', '.ts'],
25
+ /**
26
+ * Directory to be scanned
27
+ * @param {string}
28
+ * @default '.''
29
+ */
30
+ directory: '.',
31
+ /**
32
+ * Output file path to export the results
33
+ * @param {string}
34
+ */
35
+ output: null,
36
+ },
37
+ }
@@ -1,49 +1,41 @@
1
+ /* eslint-disable complexity */
2
+ /* eslint-disable max-lines-per-function */
1
3
  import * as process from 'node:process'
2
4
 
3
- import { appendFileSync, existsSync } from 'fs'
5
+ import { existsSync, mkdirSync, writeFileSync } from 'fs'
4
6
  import path from 'path'
5
7
 
8
+ import { Logger } from '../core/index.mjs'
9
+ import defaultConfig from './config.cjs'
10
+ import { loadConfig } from './loadConfig.mjs'
6
11
  import { scanCallback } from './scanCallback.mjs'
7
- import { logger, scanDirectories } from './utils/index.mjs'
12
+ import { scanDirectories } from './utils/index.mjs'
8
13
 
9
- const DEFAULT_CONFIG = {
10
- adoption: {
11
- details: false,
12
- sort: 'count',
13
- imports: ['@spark-ui'],
14
- extensions: ['.tsx', '.ts'],
15
- directory: '.',
16
- },
17
- }
14
+ export async function adoption(options = {}) {
15
+ const { configuration, ...optionsConfig } = options
18
16
 
19
- export async function adoption(options) {
20
- let config = DEFAULT_CONFIG
17
+ const logger = new Logger({ verbose: optionsConfig.verbose })
18
+ let config = await loadConfig(configuration, { logger })
21
19
 
22
- const configFileRoute = path.join(process.cwd(), options.configuration || '.spark-ui.cjs')
23
- try {
24
- if (existsSync(configFileRoute)) {
25
- logger.info('ℹ️ Loading spark-ui custom configuration file')
26
- const { default: customConfig } = await import(
27
- path.join(process.cwd(), options.configuration)
28
- )
29
- config = structuredClone(customConfig, DEFAULT_CONFIG)
30
- } else {
31
- logger.warn('⚠️ No custom configuration file found')
32
- logger.info('ℹ️ Loading default configuration')
33
- }
34
- } catch (error) {
35
- logger.error('💥 Something went wrong loading the custom configuration file')
20
+ config = {
21
+ adoption: {
22
+ ...config.adoption,
23
+ ...optionsConfig,
24
+ imports: optionsConfig.imports || config.adoption.imports,
25
+ extensions: optionsConfig.extensions || config.adoption.extensions,
26
+ },
36
27
  }
37
28
 
38
- const extensions = config.adoption.extensions
39
-
40
29
  let importCount = 0
41
30
  const importResults = {}
42
31
  let importsUsed = {}
43
32
  let importsCount = {}
44
- config.adoption.imports.forEach(moduleName => {
33
+
34
+ const { details, directory, extensions, imports, sort, output } = config.adoption
35
+
36
+ imports.forEach(moduleName => {
45
37
  logger.info(`ℹ️ Scanning adoption for ${moduleName}`)
46
- const directoryPath = path.join(process.cwd(), config.adoption.directory)
38
+ const directoryPath = path.join(process.cwd(), directory)
47
39
 
48
40
  const response = scanDirectories(directoryPath, moduleName, extensions, scanCallback, {
49
41
  importCount,
@@ -56,7 +48,7 @@ export async function adoption(options) {
56
48
  `🎉 Found ${response.importCount - importCount} imports with "${moduleName}" modules across directory ${directoryPath}.`
57
49
  )
58
50
  } else {
59
- logger.warn(
51
+ logger.warning(
60
52
  `⚠️ No files found with "${moduleName}" imports across directory ${directoryPath}.`
61
53
  )
62
54
  }
@@ -64,7 +56,7 @@ export async function adoption(options) {
64
56
  })
65
57
 
66
58
  // Sort importsUsed by alphabet
67
- if (config.adoption.sort === 'alphabetical') {
59
+ if (sort === 'alphabetical') {
68
60
  importsUsed = Object.fromEntries(
69
61
  Object.entries(importsUsed)
70
62
  .sort(([pkgNameA], [pkgNameB]) => pkgNameA.localeCompare(pkgNameB))
@@ -83,7 +75,7 @@ export async function adoption(options) {
83
75
  ]
84
76
  })
85
77
  )
86
- } else if (config.adoption.sort === 'count') {
78
+ } else if (sort === 'count') {
87
79
  // Sort importsUsed by most used
88
80
  importsUsed = Object.fromEntries(
89
81
  Object.entries(importsUsed)
@@ -110,17 +102,25 @@ export async function adoption(options) {
110
102
  const result = Object.fromEntries(
111
103
  Object.entries(importsUsed).map(([pkgName, value]) => [
112
104
  pkgName,
113
- { ...value, ...(config.adoption.details && { results: importResults[pkgName] }) },
105
+ { ...value, ...(details && { results: importResults[pkgName] }) },
114
106
  ])
115
107
  )
116
108
 
117
- if (options.output) {
109
+ if (output) {
118
110
  try {
119
- appendFileSync(`${options.output}`, JSON.stringify(result, null, 2))
120
- } catch (err) {
121
- logger.error(`💥 Error writing file: ${err}`)
111
+ const { dir } = path.parse(path.join(process.cwd(), output))
112
+ if (!existsSync(dir)) {
113
+ mkdirSync(dir, { recursive: true })
114
+ }
115
+ writeFileSync(`${path.join(process.cwd(), output)}`, JSON.stringify(result, null, 2))
116
+ } catch (error) {
117
+ logger.error(`💥 Error writing file: ${error}`)
118
+ process.exit(1)
122
119
  }
123
120
  } else {
124
- logger.info(JSON.stringify(result, null, 2))
121
+ logger.force().info(JSON.stringify(result, null, 2))
122
+ process.exit(0)
125
123
  }
126
124
  }
125
+
126
+ export const config = { ...defaultConfig }
@@ -0,0 +1,157 @@
1
+ import path from 'node:path'
2
+
3
+ import fse from 'fs-extra'
4
+ import { fileURLToPath } from 'url'
5
+ import { beforeEach, describe, expect, it } from 'vitest'
6
+
7
+ import cmd, { ENTER } from '../../test/utils/cmd'
8
+
9
+ const __dirname = fileURLToPath(import.meta.url)
10
+ const cliPath = path.join(__dirname, '../../../bin/spark.mjs')
11
+
12
+ const cliProcess = cmd.create(cliPath)
13
+
14
+ describe('CLI `spark scan`', () => {
15
+ const OUTPUT_FILE = 'report.json'
16
+
17
+ const EXT_CONFIG_FILE_PATH = 'packages/utils/cli/test/stubs/scanConfigWithCustomExtensions.js'
18
+ const SORT_CONFIG_FILE_PATH = 'packages/utils/cli/test/stubs/scanConfigWithCustomSorting.js'
19
+ const DETAILED_OUTPUT_CONFIG_FILE_PATH =
20
+ 'packages/utils/cli/test/stubs/scanConfigWithDetailedOutput.js'
21
+ const IMPORTS_CONFIG_FILE_PATH = 'packages/utils/cli/test/stubs/scanConfigWithCustomImports.js'
22
+
23
+ const adoptionCommand = async (args: string[] = []) => {
24
+ /**
25
+ * We define here some common options such as custom directory (for performance reasons).
26
+ */
27
+ return await cliProcess.execute(
28
+ ['scan', 'adoption', '-dir', './packages/components', ...args],
29
+ [ENTER]
30
+ )
31
+ }
32
+
33
+ beforeEach(() => {
34
+ if (fse.existsSync(OUTPUT_FILE)) fse.removeSync(OUTPUT_FILE)
35
+ })
36
+
37
+ afterAll(() => {
38
+ if (fse.existsSync(OUTPUT_FILE)) fse.removeSync(OUTPUT_FILE)
39
+ })
40
+
41
+ describe('Adoption (components adoption)', () => {
42
+ it('should execute command with default config', async () => {
43
+ const response = await adoptionCommand(['-v'])
44
+
45
+ expect(response).toMatch(/Loading default configuration/i)
46
+ expect(response).toMatch(/Scanning adoption for @spark-ui/i)
47
+
48
+ expect(response).toMatch(/Found/i)
49
+
50
+ /**
51
+ * With no output option the adoption report will be simply logged
52
+ */
53
+ expect(
54
+ response.filter(
55
+ (entry: string | Record<string, unknown>) =>
56
+ entry['@spark-ui/button' as keyof typeof entry]
57
+ )
58
+ ).toBeDefined()
59
+ })
60
+
61
+ describe('Results details with output', () => {
62
+ it('should output results with packages details from command option', async () => {
63
+ await adoptionCommand(['-d', '-o', OUTPUT_FILE])
64
+
65
+ expect(fse.pathExistsSync(OUTPUT_FILE)).toBe(true)
66
+
67
+ const outputContent = JSON.parse(fse.readFileSync(OUTPUT_FILE, 'utf-8'))
68
+
69
+ Object.keys(outputContent).forEach(outputItem => {
70
+ expect(Object.keys(outputContent[outputItem]).includes('results')).toBeTruthy()
71
+ })
72
+ })
73
+
74
+ it('should output results with packages details from configuration file', async () => {
75
+ await adoptionCommand(['-c', DETAILED_OUTPUT_CONFIG_FILE_PATH])
76
+
77
+ expect(fse.pathExistsSync(OUTPUT_FILE)).toBe(true)
78
+
79
+ const outputContent = JSON.parse(fse.readFileSync(OUTPUT_FILE, 'utf-8'))
80
+
81
+ Object.keys(outputContent).forEach(outputItem => {
82
+ expect(Object.keys(outputContent[outputItem]).includes('results')).toBeTruthy()
83
+ })
84
+ })
85
+ })
86
+
87
+ describe('Results sorting', () => {
88
+ it('should sort results alphabetically from command option', async () => {
89
+ const response = await adoptionCommand(['-v', '-s', 'alphabetical'])
90
+
91
+ expect(response).toMatch(/Loading default configuration/i)
92
+ expect(response).toMatch(/Scanning adoption for @spark-ui/i)
93
+
94
+ const pkgList = Object.keys(
95
+ response.filter((entry: string | Record<string, unknown>) => typeof entry !== 'string')[0]
96
+ )
97
+
98
+ const sparkInputIndex = pkgList.indexOf('@spark-ui/input')
99
+ const sparkFormFieldIndex = pkgList.indexOf('@spark-ui/form-field')
100
+
101
+ expect(sparkInputIndex).toBeGreaterThan(sparkFormFieldIndex)
102
+ })
103
+
104
+ it('should sort results alphabetically from configuration file', async () => {
105
+ const response = await adoptionCommand(['-v', '-c', SORT_CONFIG_FILE_PATH])
106
+
107
+ expect(response).toMatch(/Loading spark-ui custom configuration file/i)
108
+ expect(response).toMatch(/Scanning adoption for @spark-ui/i)
109
+
110
+ const pkgList = Object.keys(
111
+ response.filter((entry: string | Record<string, unknown>) => typeof entry !== 'string')[0]
112
+ )
113
+
114
+ const sparkInputIndex = pkgList.indexOf('@spark-ui/input')
115
+ const sparkFormFieldIndex = pkgList.indexOf('@spark-ui/form-field')
116
+
117
+ expect(sparkInputIndex).toBeGreaterThan(sparkFormFieldIndex)
118
+ })
119
+ })
120
+
121
+ describe('File extensions', () => {
122
+ it('should execute command with custom extensions from command option', async () => {
123
+ const response = await adoptionCommand(['-v', '-ext', '.css'])
124
+
125
+ expect(response).toMatch(/Loading default configuration/i)
126
+ expect(response).toMatch(/Scanning adoption for @spark-ui/i)
127
+
128
+ expect(response).toMatch(/No files found with "@spark-ui" imports across directory/i)
129
+ })
130
+
131
+ it('should execute command with custom extensions from configuration file', async () => {
132
+ const response = await adoptionCommand(['-v', '-c', EXT_CONFIG_FILE_PATH])
133
+
134
+ expect(response).toMatch(/Loading spark-ui custom configuration file/i)
135
+ expect(response).toMatch(/Scanning adoption for @spark-ui/i)
136
+
137
+ expect(response).toMatch(/No files found with "@spark-ui" imports across directory/i)
138
+ })
139
+ })
140
+
141
+ describe('Imports patterns', () => {
142
+ it('should execute command for custom import patterns from command option', async () => {
143
+ const response = await adoptionCommand(['-v', '-i', '@react-aria', '@react-stately'])
144
+
145
+ expect(response).toMatch(/Scanning adoption for @react-aria/i)
146
+ expect(response).toMatch(/Scanning adoption for @react-stately/i)
147
+ })
148
+
149
+ it('should execute command for custom import patterns from configuration file', async () => {
150
+ const response = await adoptionCommand(['-v', '-c', IMPORTS_CONFIG_FILE_PATH])
151
+
152
+ expect(response).toMatch(/Scanning adoption for @react-aria/i)
153
+ expect(response).toMatch(/Scanning adoption for @react-stately/i)
154
+ })
155
+ })
156
+ })
157
+ })
@@ -0,0 +1,41 @@
1
+ import process from 'node:process'
2
+
3
+ import { existsSync } from 'fs'
4
+ import path from 'path'
5
+
6
+ import defaultConfig from './config.cjs'
7
+
8
+ export async function loadConfig(configuration, { logger }) {
9
+ try {
10
+ const configFileRoute = path.join(process.cwd(), configuration || '.spark-ui.cjs')
11
+
12
+ if (existsSync(configFileRoute)) {
13
+ logger.info('ℹ️ Loading spark-ui custom configuration file')
14
+ const { default: customConfig } = await import(configFileRoute)
15
+
16
+ const config = {
17
+ adoption: {
18
+ ...defaultConfig.adoption,
19
+ ...customConfig.adoption,
20
+ imports: customConfig.adoption.imports || defaultConfig.adoption.imports,
21
+ extensions: customConfig.adoption.extensions || defaultConfig.adoption.extensions,
22
+ },
23
+ }
24
+
25
+ return config
26
+ } else {
27
+ if (configuration) {
28
+ logger.error('⚠️ No custom configuration file found:', configFileRoute)
29
+ process.exit(1)
30
+ }
31
+ logger.warning('⚠️ No custom configuration file found')
32
+ logger.info('ℹ️ Loading default configuration')
33
+
34
+ return { ...defaultConfig }
35
+ }
36
+ } catch (error) {
37
+ logger.error('💥 Something went wrong loading the custom configuration file', error)
38
+
39
+ return { ...defaultConfig }
40
+ }
41
+ }
@@ -1,5 +1,4 @@
1
1
  export { extractImports } from './extract-imports.mjs'
2
2
  export { fileContainsImport } from './file-contains-import.mjs'
3
3
  export { getFormatedTimestamp } from './get-formated-timestamp.mjs'
4
- export { logger } from './logger.mjs'
5
4
  export { scanDirectories } from './scan-directories.mjs'
@@ -12,21 +12,28 @@ const cliPath = path.join(__dirname, '../../bin/spark.mjs')
12
12
  const cliProcess = cmd.create(cliPath)
13
13
 
14
14
  describe('CLI `spark generate` (component package)', () => {
15
- it('should properly generate package from CLI when arguments are valid', async () => {
16
- // GIVEN a package definition
17
- const packageName = 'bar'
18
- const packageType = 'component'
15
+ const packageType = 'component'
16
+
17
+ const contextPath = TemplateGenerator.CONTEXTS[packageType] as string
18
+
19
+ const packagePath = path.join(process.cwd(), 'packages', contextPath, 'bar')
20
+ const invalidPackagePath = path.join(process.cwd(), 'packages', contextPath, '123')
21
+
22
+ beforeEach(() => {
23
+ if (fse.existsSync(packagePath)) fse.removeSync(packagePath)
24
+ })
25
+
26
+ afterAll(() => {
27
+ if (fse.existsSync(packagePath)) fse.removeSync(packagePath)
28
+ })
19
29
 
20
- // WHEN we execute the `spark generate` command
30
+ it('should properly generate package from CLI when arguments are valid', async () => {
21
31
  const response = await cliProcess.execute(
22
32
  ['generate'],
23
- [packageName, ENTER, packageType, ENTER, 'This is my foo component', ENTER]
33
+ ['bar', ENTER, packageType, ENTER, 'This is my foo component', ENTER]
24
34
  )
25
- const contextPath = TemplateGenerator.CONTEXTS[packageType] as string
26
- const packagePath = path.join(process.cwd(), 'packages', contextPath, packageName)
27
35
 
28
- // THEN each file is created and the user is informed about it
29
- const expectedFiles = [
36
+ ;[
30
37
  '/.npmignore',
31
38
  '/package.json',
32
39
  '/src/index.ts',
@@ -37,33 +44,19 @@ describe('CLI `spark generate` (component package)', () => {
37
44
  '/src/Bar.test.tsx',
38
45
  '/src/Bar.stories.tsx',
39
46
  '/tsconfig.json',
40
- ]
41
-
42
- const assertExpectedFiles = (filePath: string) => {
43
- expect(response).toContain(`Created ${packagePath}${filePath}`)
47
+ ].forEach((filePath: string) => {
48
+ expect(response.toString()).toContain(`Created ${packagePath}${filePath}`)
44
49
  expect(fse.pathExistsSync(`${packagePath}${filePath}`)).toBe(true)
45
- }
46
-
47
- expectedFiles.forEach(assertExpectedFiles)
48
-
49
- // Finally we clean up the generated component package
50
- fse.removeSync(packagePath)
50
+ })
51
51
  })
52
52
 
53
53
  it('should prevent generating package when argument are invalid', async () => {
54
- // GIVEN the package name is not using kebab-case notation
55
- const packageName = '123'
56
- const packageType = 'component'
57
-
58
- // WHEN we try to create it
59
- const response = await cliProcess.execute(['generate'], [packageName, ENTER])
60
- const contextPath = TemplateGenerator.CONTEXTS[packageType] as string
61
- const packagePath = path.join(process.cwd(), 'packages', contextPath, packageName)
54
+ const response = await cliProcess.execute(['generate'], ['123', ENTER])
62
55
 
63
- // THEN it throws an error and fails to create files for this package
64
- expect(response).toContain(
56
+ expect(response.toString()).toContain(
65
57
  'Name name must contain letters and dash symbols only (ex: "my-package")'
66
58
  )
67
- expect(fse.pathExistsSync(packagePath)).toBe(false)
59
+
60
+ expect(fse.pathExistsSync(invalidPackagePath)).toBe(false)
68
61
  })
69
62
  })
@@ -0,0 +1,10 @@
1
+ export default {
2
+ adoption: {
3
+ details: false,
4
+ sort: 'count',
5
+ imports: ['@spark-ui'],
6
+ extensions: ['.css'],
7
+ directory: '.',
8
+ output: null,
9
+ },
10
+ }
@@ -0,0 +1,10 @@
1
+ export default {
2
+ adoption: {
3
+ details: false,
4
+ sort: 'count',
5
+ imports: ['@react-aria', '@react-stately'],
6
+ extensions: ['.tsx', '.ts'],
7
+ directory: '.',
8
+ output: null,
9
+ },
10
+ }
@@ -0,0 +1,10 @@
1
+ export default {
2
+ adoption: {
3
+ details: false,
4
+ sort: 'alphabetical',
5
+ imports: ['@spark-ui'],
6
+ extensions: ['.tsx', '.ts'],
7
+ directory: '.',
8
+ output: null,
9
+ },
10
+ }
@@ -0,0 +1,10 @@
1
+ export default {
2
+ adoption: {
3
+ details: true,
4
+ sort: 'count',
5
+ imports: ['@spark-ui'],
6
+ extensions: ['.tsx', '.ts'],
7
+ directory: '.',
8
+ output: 'report.json',
9
+ },
10
+ }
package/test/utils/cmd.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  /**
2
3
  * CLI Integration Test Helper
3
4
  * @author Andrés Zorro <zorrodg@gmail.com>
@@ -11,11 +12,30 @@ import { constants } from 'os'
11
12
 
12
13
  const PATH = process.env.PATH
13
14
 
15
+ /**
16
+ * Format CMD response as a JSON object
17
+ */
18
+ function getReportFromData(data) {
19
+ const regex = /({[\s\S]*})/ // Regular expression to match object type
20
+ const match = data.match(regex)
21
+
22
+ const messages = data
23
+ .slice(0, match?.index ?? undefined)
24
+ .trim()
25
+ .split('\n')
26
+ .map(line => line.trim())
27
+ .filter(line => line)
28
+
29
+ const json = match ? JSON.parse(match[0]) : undefined
30
+
31
+ return [...messages, json]
32
+ }
33
+
14
34
  /**
15
35
  * Creates a child process with script path
16
- * @param processPath Path of the process to execute
17
- * @param args Arguments to the command
18
- * @param env (optional) Environment variables
36
+ * @param {string} processPath Path of the process to execute
37
+ * @param {Array} args Arguments to the command
38
+ * @param {Object} env (optional) Environment variables
19
39
  */
20
40
  function createProcess(processPath, args = [], env = null) {
21
41
  // Ensure that path exists
@@ -44,18 +64,18 @@ function createProcess(processPath, args = [], env = null) {
44
64
  * Creates a command and executes inputs (user responses) to the stdin
45
65
  * Returns a promise that resolves when all inputs are sent
46
66
  * Rejects the promise if any error
47
- * @param processPath Path of the process to execute
48
- * @param args Arguments to the command
49
- * @param inputs (Optional) Array of inputs (user responses)
50
- * @param opts (optional) Environment variables
67
+ * @param {string} processPath Path of the process to execute
68
+ * @param {Array} args Arguments to the command
69
+ * @param {Array} inputs (Optional) Array of inputs (user responses)
70
+ * @param {Object} opts (optional) Environment variables
51
71
  */
52
- function executeWithInput(processPath, args, inputs, opts = {}) {
72
+ function executeWithInput(processPath, args = [], inputs = [], opts = {}) {
53
73
  if (!Array.isArray(inputs)) {
54
74
  opts = inputs
55
75
  inputs = []
56
76
  }
57
77
 
58
- const { env = null, timeout = 100, maxTimeout = 10000 } = opts
78
+ const { env = null, timeout = 250, maxTimeout = 10000 } = opts
59
79
  const childProcess = createProcess(processPath, args, env)
60
80
  childProcess.stdin.setEncoding('utf-8')
61
81
 
@@ -64,12 +84,12 @@ function executeWithInput(processPath, args, inputs, opts = {}) {
64
84
  // Creates a loop to feed user inputs to the child process in order to get results from the tool
65
85
  // This code is heavily inspired (if not blantantly copied) from inquirer-test:
66
86
  // https://github.com/ewnd9/inquirer-test/blob/6e2c40bbd39a061d3e52a8b1ee52cdac88f8d7f7/index.js#L14
67
- const loop = ins => {
87
+ const loop = inputs => {
68
88
  if (killIOTimeout) {
69
89
  clearTimeout(killIOTimeout)
70
90
  }
71
91
 
72
- if (!ins.length) {
92
+ if (!inputs.length) {
73
93
  childProcess.stdin.end()
74
94
 
75
95
  // Set a timeout to wait for CLI response. If CLI takes longer than
@@ -83,12 +103,12 @@ function executeWithInput(processPath, args, inputs, opts = {}) {
83
103
  }
84
104
 
85
105
  currentInputTimeout = setTimeout(() => {
86
- childProcess.stdin.write(ins[0])
106
+ childProcess.stdin.write(inputs[0])
87
107
  // Log debug I/O statements on tests
88
108
  if (env && env.DEBUG) {
89
- console.log('input:', ins[0]) // eslint-disable-line no-console
109
+ console.log('input:', inputs[0])
90
110
  }
91
- loop(ins.slice(1))
111
+ loop(inputs.slice(1))
92
112
  }, timeout)
93
113
  }
94
114
 
@@ -97,7 +117,7 @@ function executeWithInput(processPath, args, inputs, opts = {}) {
97
117
  childProcess.stderr.on('data', data => {
98
118
  // Log debug I/O statements on tests
99
119
  if (env && env.DEBUG) {
100
- console.log('error:', data.toString()) // eslint-disable-line no-console
120
+ console.log('error:', data.toString())
101
121
  }
102
122
  })
103
123
 
@@ -105,7 +125,7 @@ function executeWithInput(processPath, args, inputs, opts = {}) {
105
125
  childProcess.stdout.on('data', data => {
106
126
  // Log debug I/O statements on tests
107
127
  if (env && env.DEBUG) {
108
- console.log('output:', data.toString()) // eslint-disable-line no-console
128
+ console.log('output:', data.toString())
109
129
  }
110
130
  })
111
131
 
@@ -130,7 +150,7 @@ function executeWithInput(processPath, args, inputs, opts = {}) {
130
150
  clearTimeout(killIOTimeout)
131
151
  }
132
152
 
133
- resolve(result.toString())
153
+ resolve(getReportFromData(result.toString()))
134
154
  })
135
155
  )
136
156
  })
@@ -1,19 +0,0 @@
1
- import chalk from 'chalk'
2
-
3
- export const logger = {
4
- error(...args) {
5
- console.log(chalk.red(...args))
6
- },
7
- warn(...args) {
8
- console.log(chalk.yellow(...args))
9
- },
10
- info(...args) {
11
- console.log(chalk.cyan(...args))
12
- },
13
- success(...args) {
14
- console.log(chalk.green(...args))
15
- },
16
- break() {
17
- console.log('')
18
- },
19
- }