@spark-ui/cli-utils 2.6.4 → 2.7.0

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,16 @@
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.7.0](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.6.4...@spark-ui/cli-utils@2.7.0) (2023-03-17)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cli-utils:** fix types ([4e4ee36](https://github.com/adevinta/spark/commit/4e4ee362b0af53c994c7b8c682d04c8160294854))
11
+
12
+ ### Features
13
+
14
+ - **cli-utils:** fix some issues ([cd0cc3e](https://github.com/adevinta/spark/commit/cd0cc3e31100316c2b544cc6ef74994e1a124fa0))
15
+
6
16
  ## [2.6.4](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.6.3...@spark-ui/cli-utils@2.6.4) (2023-03-15)
7
17
 
8
18
  **Note:** Version bump only for package @spark-ui/cli-utils
@@ -26,7 +26,7 @@ export const run = async () => {
26
26
  }
27
27
 
28
28
  const type = await prompt.select({
29
- message: 'Chose a template:',
29
+ message: 'Choose a template:',
30
30
  initialValue: 'component',
31
31
  options: [
32
32
  {
package/bin/spark.mjs CHANGED
@@ -8,7 +8,7 @@ const { version } = require('../package.json')
8
8
 
9
9
  program.version(version, '--version')
10
10
 
11
- program.command('generate <component>', 'Generate a component scaffolding').alias('g')
11
+ program.command('generate', 'Generate a component scaffolding').alias('g')
12
12
  program.command('setup-themes', 'Set up Spark theming configuration')
13
13
 
14
14
  program.parse(process.argv)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spark-ui/cli-utils",
3
- "version": "2.6.4",
3
+ "version": "2.7.0",
4
4
  "description": "Spark CLI utils",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -11,9 +11,14 @@
11
11
  "spark-setup-themes": "./bin/spark-setup-themes.mjs"
12
12
  },
13
13
  "type": "module",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git@github.com:adevinta/spark.git",
17
+ "directory": "packages/utils/cli"
18
+ },
14
19
  "dependencies": {
15
20
  "@clack/prompts": "0.6.2",
16
- "@spark-ui/theme-utils": "^2.9.0",
21
+ "@spark-ui/theme-utils": "2",
17
22
  "camel-case": "4.1.2",
18
23
  "chalk": "5.2.0",
19
24
  "commander": "10.0.0",
@@ -24,10 +29,8 @@
24
29
  "hex-rgb": "5.0.0",
25
30
  "pascal-case": "3.1.2"
26
31
  },
27
- "repository": {
28
- "type": "git",
29
- "url": "git@github.com:adevinta/spark.git",
30
- "directory": "packages/utils/cli"
32
+ "devDependencies": {
33
+ "@types/fs-extra": "11.0.1"
31
34
  },
32
- "gitHead": "7cc5991255c85d87507369e9f0e9aea5dd29e349"
35
+ "gitHead": "b02d8de6793827cb8783dcf1029c7345c7629974"
33
36
  }
@@ -19,7 +19,6 @@ export class System {
19
19
 
20
20
  getPackageJSON() {
21
21
  const basePath = this.getBasePath()
22
-
23
22
  const raw = fse.readFileSync(`${basePath}/package.json`).toString()
24
23
 
25
24
  return JSON.parse(raw)
@@ -23,6 +23,7 @@ export class TemplateGenerator extends Generator {
23
23
 
24
24
  constructor({ system }) {
25
25
  super()
26
+
26
27
  this.system = system
27
28
  }
28
29
 
@@ -0,0 +1,68 @@
1
+ import path from 'node:path'
2
+
3
+ import fse from 'fs-extra'
4
+ import { fileURLToPath } from 'url'
5
+ import { describe, expect, it } from 'vitest'
6
+
7
+ import { TemplateGenerator } from '../src/generate/generators/TemplateGenerator.mjs'
8
+ import cmd, { ENTER } from './utils/cmd'
9
+
10
+ const __dirname = fileURLToPath(import.meta.url)
11
+ const cliPath = path.join(__dirname, '../../bin/spark.mjs')
12
+ const cliProcess = cmd.create(cliPath)
13
+
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'
19
+
20
+ // WHEN we execute the `spark generate` command
21
+ const response = await cliProcess.execute(
22
+ ['generate'],
23
+ [packageName, ENTER, packageType, ENTER, 'This is my foo component', ENTER]
24
+ )
25
+ const contextPath = TemplateGenerator.CONTEXTS[packageType] as string
26
+ const packagePath = path.join(process.cwd(), 'packages', contextPath, packageName)
27
+
28
+ // THEN each file is created and the user is informed about it
29
+ const expectedFiles = [
30
+ '/.npmignore',
31
+ '/package.json',
32
+ '/src/index.ts',
33
+ '/src/Bar.variants.tsx',
34
+ '/src/Bar.tsx',
35
+ '/vite.config.ts',
36
+ '/src/Bar.stories.mdx',
37
+ '/src/Bar.test.tsx',
38
+ '/src/Bar.stories.tsx',
39
+ '/tsconfig.json',
40
+ ]
41
+ const assertExpectedFiles = (filePath: string) => {
42
+ expect(response).toContain(`Created ${packagePath}${filePath}`)
43
+ expect(fse.pathExistsSync(`${packagePath}${filePath}`)).toBe(true)
44
+ }
45
+
46
+ expectedFiles.forEach(assertExpectedFiles)
47
+
48
+ // Finally we clean up the generated component package
49
+ fse.removeSync(packagePath)
50
+ })
51
+
52
+ it('should prevent generating package when argument are invalid', async () => {
53
+ // GIVEN the package name is not using kebab-case notation
54
+ const packageName = '123'
55
+ const packageType = 'component'
56
+
57
+ // WHEN we try to create it
58
+ const response = await cliProcess.execute(['generate'], [packageName, ENTER])
59
+ const contextPath = TemplateGenerator.CONTEXTS[packageType] as string
60
+ const packagePath = path.join(process.cwd(), 'packages', contextPath, packageName)
61
+
62
+ // THEN it throws an error and fails to create files for this package
63
+ expect(response).toContain(
64
+ 'Name name must contain letters and dash symbols only (ex: "my-package")'
65
+ )
66
+ expect(fse.pathExistsSync(packagePath)).toBe(false)
67
+ })
68
+ })
@@ -0,0 +1,158 @@
1
+ /**
2
+ * CLI Integration Test Helper
3
+ * @author Andrés Zorro <zorrodg@gmail.com>
4
+ * @see https://gist.github.com/zorrodg/c349cf54a3f6d0a9ba62e0f4066f31cb
5
+ */
6
+
7
+ import concat from 'concat-stream'
8
+ import spawn from 'cross-spawn'
9
+ import { existsSync } from 'fs'
10
+ import { constants } from 'os'
11
+
12
+ const PATH = process.env.PATH
13
+
14
+ /**
15
+ * 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
19
+ */
20
+ function createProcess(processPath, args = [], env = null) {
21
+ // Ensure that path exists
22
+ if (!processPath || !existsSync(processPath)) {
23
+ throw new Error('Invalid process path')
24
+ }
25
+
26
+ args = [processPath].concat(args)
27
+
28
+ // This works for node based CLIs, but can easily be adjusted to
29
+ // any other process installed in the system
30
+ return spawn('node', args, {
31
+ env: Object.assign(
32
+ {
33
+ NODE_ENV: 'test',
34
+ preventAutoStart: false,
35
+ PATH, // This is needed in order to get all the binaries in your current terminal
36
+ },
37
+ env
38
+ ),
39
+ stdio: [null, null, null, 'ipc'], // This enables interprocess communication (IPC)
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Creates a command and executes inputs (user responses) to the stdin
45
+ * Returns a promise that resolves when all inputs are sent
46
+ * 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
51
+ */
52
+ function executeWithInput(processPath, args, inputs, opts = {}) {
53
+ if (!Array.isArray(inputs)) {
54
+ opts = inputs
55
+ inputs = []
56
+ }
57
+
58
+ const { env = null, timeout = 100, maxTimeout = 10000 } = opts
59
+ const childProcess = createProcess(processPath, args, env)
60
+ childProcess.stdin.setEncoding('utf-8')
61
+
62
+ let currentInputTimeout, killIOTimeout
63
+
64
+ // Creates a loop to feed user inputs to the child process in order to get results from the tool
65
+ // This code is heavily inspired (if not blantantly copied) from inquirer-test:
66
+ // https://github.com/ewnd9/inquirer-test/blob/6e2c40bbd39a061d3e52a8b1ee52cdac88f8d7f7/index.js#L14
67
+ const loop = ins => {
68
+ if (killIOTimeout) {
69
+ clearTimeout(killIOTimeout)
70
+ }
71
+
72
+ if (!ins.length) {
73
+ childProcess.stdin.end()
74
+
75
+ // Set a timeout to wait for CLI response. If CLI takes longer than
76
+ // maxTimeout to respond, kill the childProcess and notify user
77
+ killIOTimeout = setTimeout(() => {
78
+ console.error('Error: Reached I/O timeout')
79
+ childProcess.kill(constants.signals.SIGTERM)
80
+ }, maxTimeout)
81
+
82
+ return
83
+ }
84
+
85
+ currentInputTimeout = setTimeout(() => {
86
+ childProcess.stdin.write(ins[0])
87
+ // Log debug I/O statements on tests
88
+ if (env && env.DEBUG) {
89
+ console.log('input:', ins[0]) // eslint-disable-line no-console
90
+ }
91
+ loop(ins.slice(1))
92
+ }, timeout)
93
+ }
94
+
95
+ const promise = new Promise((resolve, reject) => {
96
+ // Get errors from CLI
97
+ childProcess.stderr.on('data', data => {
98
+ // Log debug I/O statements on tests
99
+ if (env && env.DEBUG) {
100
+ console.log('error:', data.toString()) // eslint-disable-line no-console
101
+ }
102
+ })
103
+
104
+ // Get output from CLI
105
+ childProcess.stdout.on('data', data => {
106
+ // Log debug I/O statements on tests
107
+ if (env && env.DEBUG) {
108
+ console.log('output:', data.toString()) // eslint-disable-line no-console
109
+ }
110
+ })
111
+
112
+ childProcess.stderr.once('data', err => {
113
+ childProcess.stdin.end()
114
+
115
+ if (currentInputTimeout) {
116
+ clearTimeout(currentInputTimeout)
117
+ inputs = []
118
+ }
119
+ reject(err.toString())
120
+ })
121
+
122
+ childProcess.on('error', reject)
123
+
124
+ // Kick off the process
125
+ loop(inputs)
126
+
127
+ childProcess.stdout.pipe(
128
+ concat(result => {
129
+ if (killIOTimeout) {
130
+ clearTimeout(killIOTimeout)
131
+ }
132
+
133
+ resolve(result.toString())
134
+ })
135
+ )
136
+ })
137
+
138
+ // Appending the process to the promise, in order to
139
+ // add additional parameters or behavior (such as IPC communication)
140
+ promise.attachedProcess = childProcess
141
+
142
+ return promise
143
+ }
144
+
145
+ export default {
146
+ create: processPath => {
147
+ const fn = (...args) => executeWithInput(processPath, ...args)
148
+
149
+ return {
150
+ execute: fn,
151
+ }
152
+ },
153
+ }
154
+
155
+ export const DOWN = '\x1B\x5B\x42'
156
+ export const UP = '\x1B\x5B\x41'
157
+ export const ENTER = '\x0D'
158
+ export const SPACE = '\x20'