@spark-ui/cli-utils 1.1.1 → 1.2.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 +6 -0
- package/bin/spark-generate.mjs +139 -39
- package/package.json +3 -2
- package/utils.js +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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
|
+
# [1.2.0](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@1.1.1...@spark-ui/cli-utils@1.2.0) (2023-02-15)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **cli-utils:** prompt system for spark generate command ([12aec9b](https://github.com/adevinta/spark/commit/12aec9bb2af9aacdf2337e0de92e97ea608db962)), closes [#237](https://github.com/adevinta/spark/issues/237) [#237](https://github.com/adevinta/spark/issues/237)
|
|
11
|
+
|
|
6
12
|
## [1.1.1](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@1.1.0...@spark-ui/cli-utils@1.1.1) (2023-02-15)
|
|
7
13
|
|
|
8
14
|
### Bug Fixes
|
package/bin/spark-generate.mjs
CHANGED
|
@@ -1,56 +1,156 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import fse from 'fs-extra'
|
|
5
|
+
import * as prompt from '@clack/prompts'
|
|
3
6
|
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { program } from 'commander'
|
|
5
7
|
import glob from 'glob'
|
|
6
8
|
import { pascalCase } from 'pascal-case'
|
|
7
9
|
import { log, showError, writeFile } from '../utils.js'
|
|
8
10
|
|
|
9
11
|
const BASE_DIR = process.cwd()
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
const rawRootPackageJSON = fse.readFileSync(`${BASE_DIR}/package.json`)
|
|
13
|
+
let rootPackageJSON = JSON.parse(rawRootPackageJSON)
|
|
14
|
+
|
|
15
|
+
const TEMPLATE_TYPE = {
|
|
16
|
+
COMPONENT: 'component',
|
|
17
|
+
HOOK: 'hook',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const WORKSPACES = {
|
|
21
|
+
[TEMPLATE_TYPE.COMPONENT]: '/packages/components',
|
|
22
|
+
[TEMPLATE_TYPE.HOOK]: '/packages/hooks',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ERRORS = {
|
|
26
|
+
ABORT: 'Aborted package generation',
|
|
27
|
+
NO_PKG_NAME: 'Package name must me defined',
|
|
28
|
+
INVALID_PKG_NAME: 'Name name must contain letters and dash symbols only (ex: "my-package")',
|
|
29
|
+
INVALID_DESCRIPTION: 'Description is too short (minimum is 10 chars)',
|
|
30
|
+
PKG_ALREADY_EXISTS:
|
|
31
|
+
'A package with that name already exists. Either delete it manually or use another name.',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const packageUtils = {
|
|
35
|
+
/** Validate the format of the package name (kebab case format) */
|
|
36
|
+
hasValidName: name => /^[a-z-]*$/.test(name),
|
|
37
|
+
/** Check that a package of the same name does not exists across all workspaces */
|
|
38
|
+
alreadyExists: name => {
|
|
39
|
+
return rootPackageJSON.workspaces.some(workspace => {
|
|
40
|
+
const existingPackages = glob.sync(`${BASE_DIR}/${workspace}/`)
|
|
41
|
+
return existingPackages.some(path => path.endsWith(`/${name}/`))
|
|
42
|
+
})
|
|
43
|
+
},
|
|
44
|
+
/** Retrieves the target folder of the generated package */
|
|
45
|
+
getDirectory: (name, template) => `${WORKSPACES[template]}/${name}/`,
|
|
46
|
+
/** Retrieves the full path to the folder of the generated package */
|
|
47
|
+
getFullPath: (name, template) => `${BASE_DIR}${packageUtils.getDirectory(name, template)}`,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function promptPackageName() {
|
|
51
|
+
const name = await prompt.text({
|
|
52
|
+
message: 'Package name (must contain letters and dash symbols only):',
|
|
53
|
+
initialValue: '',
|
|
54
|
+
validate(value) {
|
|
55
|
+
if (value == null) return ERRORS.NO_PKG_NAME
|
|
56
|
+
if (!packageUtils.hasValidName(value)) return ERRORS.INVALID_PKG_NAME
|
|
57
|
+
if (packageUtils.alreadyExists(value)) return ERRORS.PKG_ALREADY_EXISTS
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
if (prompt.isCancel(name)) showError(ERRORS.ABORT)
|
|
62
|
+
|
|
63
|
+
return name
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function promptPackageTemplate() {
|
|
67
|
+
const template = await prompt.select({
|
|
68
|
+
message: 'Chose a template:',
|
|
69
|
+
initialValue: TEMPLATE_TYPE.COMPONENT,
|
|
70
|
+
options: [
|
|
71
|
+
{
|
|
72
|
+
value: TEMPLATE_TYPE.COMPONENT,
|
|
73
|
+
label: 'Component',
|
|
74
|
+
hint: 'Typescript dummy component with some tests, stories and config files',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
value: TEMPLATE_TYPE.HOOK,
|
|
78
|
+
label: 'Hook',
|
|
79
|
+
hint: 'Typescript hook with some tests, stories and config files',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
20
82
|
})
|
|
21
|
-
.parse(process.argv)
|
|
22
83
|
|
|
23
|
-
|
|
24
|
-
const { description = '' } = program.opts()
|
|
84
|
+
if (prompt.isCancel(template)) showError(ERRORS.ABORT)
|
|
25
85
|
|
|
26
|
-
|
|
27
|
-
showError('Argument "component" must be defined!')
|
|
86
|
+
return template
|
|
28
87
|
}
|
|
29
|
-
|
|
30
|
-
|
|
88
|
+
|
|
89
|
+
async function promptPackageDescription() {
|
|
90
|
+
const description = await prompt.text({
|
|
91
|
+
message: 'Describe your package (short description):',
|
|
92
|
+
initialValue: '',
|
|
93
|
+
validate(value) {
|
|
94
|
+
if (!value) return `You package must have a description`
|
|
95
|
+
if (value.length < 10) return ERRORS.INVALID_DESCRIPTION
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
if (prompt.isCancel(description)) showError(ERRORS.ABORT)
|
|
100
|
+
|
|
101
|
+
return description
|
|
31
102
|
}
|
|
32
103
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Program starts here
|
|
106
|
+
*/
|
|
107
|
+
prompt.intro(`Generate Spark package`)
|
|
36
108
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const templateContents = res.map(templatePath =>
|
|
41
|
-
import(templatePath).then(module => ({
|
|
42
|
-
path: templatePath
|
|
43
|
-
.replace(/(.*)\/templates\//, componentPath)
|
|
44
|
-
.replace('Component', pascalCase(component))
|
|
45
|
-
.replaceAll(/\[|\]|\.js$/g, ''),
|
|
46
|
-
content: module.default({ component, description }),
|
|
47
|
-
}))
|
|
48
|
-
)
|
|
109
|
+
const name = await promptPackageName()
|
|
110
|
+
const template = await promptPackageTemplate()
|
|
111
|
+
const description = await promptPackageDescription()
|
|
49
112
|
|
|
50
|
-
|
|
113
|
+
const packagePath = packageUtils.getFullPath(name, template)
|
|
51
114
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
115
|
+
switch (template) {
|
|
116
|
+
case TEMPLATE_TYPE.COMPONENT:
|
|
117
|
+
generateComponentPackage(name, description)
|
|
118
|
+
break
|
|
119
|
+
case TEMPLATE_TYPE.HOOK:
|
|
120
|
+
generateHookPackage(name, description)
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
prompt.outro(`Generating package...`)
|
|
125
|
+
|
|
126
|
+
function generateComponentPackage(name, description) {
|
|
127
|
+
const templatesPattern = fileURLToPath(new URL('../templates/**/*.js', import.meta.url))
|
|
128
|
+
|
|
129
|
+
glob(templatesPattern, async (err, res) => {
|
|
130
|
+
if (err) showError(err)
|
|
131
|
+
if (res) {
|
|
132
|
+
const templateContents = res.map(templatePath =>
|
|
133
|
+
import(templatePath).then(module => ({
|
|
134
|
+
path: templatePath
|
|
135
|
+
.replace(/(.*)\/templates\//, packagePath)
|
|
136
|
+
.replace('Component', pascalCase(name))
|
|
137
|
+
.replaceAll(/\[|\]|\.js$/g, ''),
|
|
138
|
+
content: module.default({
|
|
139
|
+
component: name,
|
|
140
|
+
description: description,
|
|
141
|
+
}),
|
|
142
|
+
}))
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const filesToWrite = await Promise.all(templateContents)
|
|
146
|
+
|
|
147
|
+
Promise.all(filesToWrite.map(writeFile)).then(() => {
|
|
148
|
+
log.success('All package files has been properly written!')
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function generateHookPackage(name, description) {
|
|
155
|
+
showError('Todo: template for hook packages is not ready yet.')
|
|
156
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spark-ui/cli-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Spark CLI utils",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
|
13
13
|
"devDependencies": {
|
|
14
|
+
"@clack/prompts": "^0.2.2",
|
|
14
15
|
"chalk": "5.2.0",
|
|
15
16
|
"commander": "10.0.0",
|
|
16
17
|
"fs-extra": "11.1.0",
|
|
17
18
|
"glob": "8.1.0",
|
|
18
19
|
"pascal-case": "3.1.2"
|
|
19
20
|
},
|
|
20
|
-
"gitHead": "
|
|
21
|
+
"gitHead": "3df014946de14807674052ae9a6624a11b9c725b"
|
|
21
22
|
}
|
package/utils.js
CHANGED
|
@@ -3,15 +3,20 @@ import fs from 'node:fs'
|
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
import fse from 'fs-extra'
|
|
5
5
|
|
|
6
|
-
export const log =
|
|
6
|
+
export const log = {
|
|
7
|
+
success: msg => console.log(chalk.green(msg)), // eslint-disable-line no-console
|
|
8
|
+
error: msg => console.log(chalk.red(msg)), // eslint-disable-line no-console
|
|
9
|
+
info: msg => console.log(chalk.yellow(msg)), // eslint-disable-line no-console
|
|
10
|
+
warning: msg => console.log(chalk.orange(msg)), // eslint-disable-line no-console
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
export const showError = (msg, foreignProgram) => {
|
|
9
|
-
|
|
14
|
+
log.error(`✖ Error: ${msg}\n`)
|
|
10
15
|
process.exit(1)
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export const writeFile = ({ path, content }) =>
|
|
14
19
|
fse
|
|
15
20
|
.outputFile(path, content)
|
|
16
|
-
.then(() => log(`Created ${path}`))
|
|
21
|
+
.then(() => log.info(`Created ${path}`))
|
|
17
22
|
.catch(() => showError(`Failed creating ${path}`))
|