@spark-ui/cli-utils 2.6.4 → 2.7.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 +14 -0
- package/bin/spark-generate.mjs +1 -1
- package/bin/spark.mjs +1 -1
- package/package.json +10 -7
- package/src/core/System.mjs +0 -1
- package/src/generate/generators/TemplateGenerator.mjs +1 -0
- package/test/spark-generate.test.ts +68 -0
- package/test/utils/cmd.js +158 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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.1](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.7.0...@spark-ui/cli-utils@2.7.1) (2023-03-19)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @spark-ui/cli-utils
|
|
9
|
+
|
|
10
|
+
# [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)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **cli-utils:** fix types ([4e4ee36](https://github.com/adevinta/spark/commit/4e4ee362b0af53c994c7b8c682d04c8160294854))
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- **cli-utils:** fix some issues ([cd0cc3e](https://github.com/adevinta/spark/commit/cd0cc3e31100316c2b544cc6ef74994e1a124fa0))
|
|
19
|
+
|
|
6
20
|
## [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
21
|
|
|
8
22
|
**Note:** Version bump only for package @spark-ui/cli-utils
|
package/bin/spark-generate.mjs
CHANGED
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
|
|
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.
|
|
3
|
+
"version": "2.7.1",
|
|
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.
|
|
21
|
+
"@spark-ui/theme-utils": "^2.9.1",
|
|
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
|
-
"
|
|
28
|
-
"
|
|
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": "
|
|
35
|
+
"gitHead": "d74864d0a900c8bab3d505cb796fb2cb51527a61"
|
|
33
36
|
}
|
package/src/core/System.mjs
CHANGED
|
@@ -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'
|