@spark-ui/cli-utils 1.3.2 → 2.0.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 +21 -0
- package/bin/core/Logger.mjs +19 -0
- package/bin/core/System.mjs +45 -0
- package/bin/core/index.mjs +2 -0
- package/bin/generators/Generator.mjs +5 -0
- package/bin/generators/TemplateGenerator.mjs +83 -0
- package/bin/generators/index.mjs +2 -0
- package/bin/spark-generate.mjs +35 -121
- package/bin/spark-setup-theme.mjs +52 -21
- package/bin/validators/DescriptionValidator.mjs +19 -0
- package/bin/validators/NameValidator.mjs +25 -0
- package/bin/validators/Validator.mjs +5 -0
- package/bin/validators/index.mjs +2 -0
- package/package.json +4 -2
- package/templates/{[.npmignore].js → component/[.npmignore].js} +0 -0
- package/templates/{[package.json].js → component/[package.json].js} +2 -2
- package/templates/{[tsconfig.json].js → component/[tsconfig.json].js} +0 -0
- package/templates/{[vite.config.ts].js → component/[vite.config.ts].js} +0 -0
- package/templates/{src → component/src}/[Component.stories.mdx].js +4 -4
- package/templates/{src → component/src}/[Component.stories.tsx].js +2 -2
- package/templates/{src → component/src}/[Component.test.tsx].js +2 -2
- package/templates/{src → component/src}/[Component.tsx].js +2 -2
- package/templates/component/src/[Component.variants.tsx].js +8 -0
- package/templates/{src → component/src}/[index.ts].js +2 -2
- package/templates/hook/[.npmignore].js +3 -0
- package/templates/hook/[package.json].js +15 -0
- package/templates/hook/[tsconfig.json].js +4 -0
- package/templates/hook/[vite.config.ts].js +7 -0
- package/templates/hook/src/[index.ts].js +8 -0
- package/templates/hook/src/[name.stories.mdx].js +42 -0
- package/templates/hook/src/[name.test.tsx].js +17 -0
- package/templates/hook/src/[name.tsx].js +10 -0
- package/templates/util/[.npmignore].js +1 -0
- package/templates/util/[package.json].js +15 -0
- package/templates/util/[tsconfig.json].js +5 -0
- package/templates/util/[vite.config.ts].js +23 -0
- package/templates/util/src/[index.ts].js +1 -0
- package/utils/setupTheme/createCSSTokenfile.js +95 -0
- package/utils/setupTheme/createTailwindThemeConfigFile.js +60 -0
- package/utils/setupTheme/index.js +2 -0
- package/utils/setupTheme/utils.js +30 -0
- package/templates/src/[Component.variants.tsx].js +0 -8
- package/utils.js +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
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.0.0](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@1.4.0...@spark-ui/cli-utils@2.0.0) (2023-02-22)
|
|
7
|
+
|
|
8
|
+
### Code Refactoring
|
|
9
|
+
|
|
10
|
+
- **cli-utils:** clean up code following PR feedbacks ([724c83e](https://github.com/adevinta/spark/commit/724c83e771307addeabd1fcea45b810d56f41e2f))
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **cli-utils:** optimize DS configuration for spark consumers ([8d18892](https://github.com/adevinta/spark/commit/8d188920cffc3d24f96b1d5a9398eb22149c1640))
|
|
15
|
+
|
|
16
|
+
### BREAKING CHANGES
|
|
17
|
+
|
|
18
|
+
- **cli-utils:** update the way the cli spark-setup-theme command works
|
|
19
|
+
- **cli-utils:** update the way the cli spark-setup-theme command works
|
|
20
|
+
|
|
21
|
+
# [1.4.0](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@1.3.2...@spark-ui/cli-utils@1.4.0) (2023-02-22)
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
- **cli-utils:** add hook and utils template ([e22d672](https://github.com/adevinta/spark/commit/e22d672e349909cc4bc8673312846d10d77e7ea5))
|
|
26
|
+
|
|
6
27
|
## [1.3.2](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@1.3.1...@spark-ui/cli-utils@1.3.2) (2023-02-22)
|
|
7
28
|
|
|
8
29
|
**Note:** Version bump only for package @spark-ui/cli-utils
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
export class Logger {
|
|
4
|
+
success(message) {
|
|
5
|
+
console.log(chalk.green(message))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
error(message) {
|
|
9
|
+
console.log(chalk.red(message))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
info(message) {
|
|
13
|
+
console.log(chalk.yellow(message))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
warning(message) {
|
|
17
|
+
console.log(chalk.green(message))
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import glob from 'glob'
|
|
3
|
+
|
|
4
|
+
export class System {
|
|
5
|
+
logger
|
|
6
|
+
|
|
7
|
+
constructor({ logger }) {
|
|
8
|
+
this.logger = logger
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exit(message) {
|
|
12
|
+
this.logger.error(`✖ Error: ${message}\n`)
|
|
13
|
+
process.exit(1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getBasePath() {
|
|
17
|
+
return process.cwd()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getPackageJSON() {
|
|
21
|
+
const basePath = this.getBasePath()
|
|
22
|
+
|
|
23
|
+
const raw = fse.readFileSync(`${basePath}/package.json`).toString()
|
|
24
|
+
|
|
25
|
+
return JSON.parse(raw)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isPackageCreated(name) {
|
|
29
|
+
const base = this.getBasePath()
|
|
30
|
+
const packageJSON = this.getPackageJSON()
|
|
31
|
+
|
|
32
|
+
return packageJSON.workspaces.some(workspace => {
|
|
33
|
+
const packages = glob.sync(`${base}/${workspace}/`)
|
|
34
|
+
|
|
35
|
+
return packages.some(path => path.endsWith(`/${name}/`))
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
writeFile({ path, content }) {
|
|
40
|
+
return fse
|
|
41
|
+
.outputFile(path, content)
|
|
42
|
+
.then(() => this.logger.info(`Created ${path}`))
|
|
43
|
+
.catch(error => this.exit(`Failed creating ${path}`))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import glob from 'glob'
|
|
4
|
+
import { pascalCase } from 'pascal-case'
|
|
5
|
+
import { camelCase } from 'camel-case'
|
|
6
|
+
|
|
7
|
+
import { System } from '../core/index.mjs'
|
|
8
|
+
import { Generator } from './Generator.mjs'
|
|
9
|
+
|
|
10
|
+
export class TemplateGenerator extends Generator {
|
|
11
|
+
static TYPES = {
|
|
12
|
+
COMPONENT: 'component',
|
|
13
|
+
HOOK: 'hook',
|
|
14
|
+
UTIL: 'util',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static CONTEXTS = {
|
|
18
|
+
[TemplateGenerator.TYPES.COMPONENT]: 'components',
|
|
19
|
+
[TemplateGenerator.TYPES.HOOK]: 'components',
|
|
20
|
+
[TemplateGenerator.TYPES.UTIL]: 'utils',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor({ system }) {
|
|
24
|
+
super()
|
|
25
|
+
this.system = system
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getDest({ type, name }) {
|
|
29
|
+
const basePath = this.system.getBasePath()
|
|
30
|
+
const context = TemplateGenerator.CONTEXTS[type]
|
|
31
|
+
|
|
32
|
+
return `${basePath}/packages/${context}/${name}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getTemplatePaths({ type }) {
|
|
36
|
+
const pattern = fileURLToPath(new URL(`../../templates/${type}/**/*.js`, import.meta.url))
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
glob(pattern, async (error, paths) => {
|
|
40
|
+
if (error) {
|
|
41
|
+
return reject(error)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
resolve(paths)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getTemplatePath({ path, name, type, dest }) {
|
|
50
|
+
const parsed = path
|
|
51
|
+
.replace(/(.*)\/templates\/([a-z-]+)\//, `${dest}/`)
|
|
52
|
+
.replaceAll(/\[|\]|\.js$/g, '')
|
|
53
|
+
|
|
54
|
+
if (type === TemplateGenerator.TYPES.COMPONENT) {
|
|
55
|
+
return parsed.replace('Component', pascalCase(name))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (type === TemplateGenerator.TYPES.HOOK) {
|
|
59
|
+
return parsed.replace('name', camelCase(name))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return parsed
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async execute({ type, name, description }) {
|
|
66
|
+
const dest = this.getDest({ type, name })
|
|
67
|
+
const paths = await this.getTemplatePaths({ type })
|
|
68
|
+
|
|
69
|
+
const promises = paths.map(path =>
|
|
70
|
+
import(path).then(module => ({
|
|
71
|
+
path: this.getTemplatePath({ path, name, type, dest }),
|
|
72
|
+
content: module.default({
|
|
73
|
+
name,
|
|
74
|
+
description,
|
|
75
|
+
}),
|
|
76
|
+
}))
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const files = await Promise.all(promises)
|
|
80
|
+
|
|
81
|
+
return Promise.all(files.map(file => this.system.writeFile(file)))
|
|
82
|
+
}
|
|
83
|
+
}
|
package/bin/spark-generate.mjs
CHANGED
|
@@ -1,157 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
import fse from 'fs-extra'
|
|
5
3
|
import * as prompt from '@clack/prompts'
|
|
6
|
-
import { fileURLToPath } from 'node:url'
|
|
7
|
-
import glob from 'glob'
|
|
8
|
-
import { pascalCase } from 'pascal-case'
|
|
9
|
-
import { log, showError, writeFile } from '../utils.js'
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
import { TemplateGenerator } from './generators/index.mjs'
|
|
6
|
+
import { Logger, System } from './core/index.mjs'
|
|
7
|
+
import { DescriptionValidator, NameValidator } from './validators/index.mjs'
|
|
14
8
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
42
|
-
return existingPackages.some(path => path.endsWith(`/${name}/`))
|
|
43
|
-
})
|
|
44
|
-
},
|
|
45
|
-
/** Retrieves the target folder of the generated package */
|
|
46
|
-
getDirectory: (name, template) => `${WORKSPACES[template]}/${name}/`,
|
|
47
|
-
/** Retrieves the full path to the folder of the generated package */
|
|
48
|
-
getFullPath: (name, template) => `${BASE_DIR}${packageUtils.getDirectory(name, template)}`,
|
|
49
|
-
}
|
|
9
|
+
const logger = new Logger()
|
|
10
|
+
const system = new System({ logger })
|
|
11
|
+
const generator = new TemplateGenerator({ system })
|
|
50
12
|
|
|
51
|
-
async
|
|
13
|
+
export const run = async () => {
|
|
52
14
|
const name = await prompt.text({
|
|
53
15
|
message: 'Package name (must contain letters and dash symbols only):',
|
|
54
16
|
initialValue: '',
|
|
55
17
|
validate(value) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
18
|
+
const validator = new NameValidator({ system })
|
|
19
|
+
|
|
20
|
+
return validator.validate(value)
|
|
59
21
|
},
|
|
60
22
|
})
|
|
61
23
|
|
|
62
|
-
if (prompt.isCancel(name))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
24
|
+
if (prompt.isCancel(name)) {
|
|
25
|
+
system.exit('Aborted package generation')
|
|
26
|
+
}
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
const template = await prompt.select({
|
|
28
|
+
const type = await prompt.select({
|
|
69
29
|
message: 'Chose a template:',
|
|
70
|
-
initialValue:
|
|
30
|
+
initialValue: 'component',
|
|
71
31
|
options: [
|
|
72
32
|
{
|
|
73
|
-
value:
|
|
33
|
+
value: TemplateGenerator.TYPES.COMPONENT,
|
|
74
34
|
label: 'Component',
|
|
75
|
-
hint: '
|
|
35
|
+
hint: 'TypeScript component package',
|
|
76
36
|
},
|
|
77
37
|
{
|
|
78
|
-
value:
|
|
38
|
+
value: TemplateGenerator.TYPES.HOOK,
|
|
79
39
|
label: 'Hook',
|
|
80
|
-
hint: '
|
|
40
|
+
hint: 'TypeScript hook package',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: TemplateGenerator.TYPES.UTIL,
|
|
44
|
+
label: 'Utility',
|
|
45
|
+
hint: 'TypeScript utility package',
|
|
81
46
|
},
|
|
82
47
|
],
|
|
83
48
|
})
|
|
84
49
|
|
|
85
|
-
if (prompt.isCancel(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
50
|
+
if (prompt.isCancel(type)) {
|
|
51
|
+
system.exit('Aborted package generation')
|
|
52
|
+
}
|
|
89
53
|
|
|
90
|
-
async function promptPackageDescription() {
|
|
91
54
|
const description = await prompt.text({
|
|
92
55
|
message: 'Describe your package (short description):',
|
|
93
56
|
initialValue: '',
|
|
94
57
|
validate(value) {
|
|
95
|
-
|
|
96
|
-
|
|
58
|
+
const validator = new DescriptionValidator()
|
|
59
|
+
|
|
60
|
+
return validator.validate(value)
|
|
97
61
|
},
|
|
98
62
|
})
|
|
99
63
|
|
|
100
|
-
if (prompt.isCancel(description))
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Program starts here
|
|
107
|
-
*/
|
|
108
|
-
prompt.intro(`Generate Spark package`)
|
|
109
|
-
|
|
110
|
-
const name = await promptPackageName()
|
|
111
|
-
const template = await promptPackageTemplate()
|
|
112
|
-
const description = await promptPackageDescription()
|
|
113
|
-
|
|
114
|
-
const packagePath = packageUtils.getFullPath(name, template)
|
|
115
|
-
|
|
116
|
-
switch (template) {
|
|
117
|
-
case TEMPLATE_TYPE.COMPONENT:
|
|
118
|
-
generateComponentPackage(name, description)
|
|
119
|
-
break
|
|
120
|
-
case TEMPLATE_TYPE.HOOK:
|
|
121
|
-
generateHookPackage(name, description)
|
|
122
|
-
break
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
prompt.outro(`Generating package...`)
|
|
126
|
-
|
|
127
|
-
function generateComponentPackage(name, description) {
|
|
128
|
-
const templatesPattern = fileURLToPath(new URL('../templates/**/*.js', import.meta.url))
|
|
129
|
-
|
|
130
|
-
glob(templatesPattern, async (err, res) => {
|
|
131
|
-
if (err) showError(err)
|
|
132
|
-
if (res) {
|
|
133
|
-
const templateContents = res.map(templatePath =>
|
|
134
|
-
import(templatePath).then(module => ({
|
|
135
|
-
path: templatePath
|
|
136
|
-
.replace(/(.*)\/templates\//, packagePath)
|
|
137
|
-
.replace('Component', pascalCase(name))
|
|
138
|
-
.replaceAll(/\[|\]|\.js$/g, ''),
|
|
139
|
-
content: module.default({
|
|
140
|
-
component: name,
|
|
141
|
-
description: description,
|
|
142
|
-
}),
|
|
143
|
-
}))
|
|
144
|
-
)
|
|
64
|
+
if (prompt.isCancel(description)) {
|
|
65
|
+
system.exit('Aborted package generation')
|
|
66
|
+
}
|
|
145
67
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Promise.all(filesToWrite.map(writeFile)).then(() => {
|
|
149
|
-
log.success('All package files has been properly written!')
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
})
|
|
68
|
+
generator.execute({ name, type, description })
|
|
153
69
|
}
|
|
154
70
|
|
|
155
|
-
|
|
156
|
-
showError('Todo: template for hook packages is not ready yet.')
|
|
157
|
-
}
|
|
71
|
+
run()
|
|
@@ -1,47 +1,78 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'child_process'
|
|
4
|
-
import { join, extname
|
|
5
|
-
import { readFileSync, readdirSync, writeFileSync, unlinkSync } from 'fs'
|
|
4
|
+
import { join, extname } from 'node:path'
|
|
5
|
+
import { readFileSync, readdirSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
6
6
|
import { transformSync } from 'esbuild'
|
|
7
|
+
import { createRequire } from 'node:module'
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
+
import { Logger, System } from './core/index.mjs'
|
|
10
|
+
import { createCSSTokensFile, createTailwindThemeConfigFile } from '../utils/setupTheme/index.js'
|
|
9
11
|
|
|
10
|
-
const
|
|
12
|
+
const logger = new Logger()
|
|
13
|
+
const system = new System({ logger })
|
|
11
14
|
|
|
15
|
+
const require = createRequire(import.meta.url)
|
|
12
16
|
const configFile = readdirSync(process.cwd()).find(fileName =>
|
|
13
17
|
/^(spark\.theme\.config)\.(js|ts|mjs|mts|cjs|cts)$/.test(fileName)
|
|
14
18
|
)
|
|
15
19
|
|
|
16
20
|
if (!configFile) {
|
|
17
|
-
|
|
21
|
+
system.exit(
|
|
18
22
|
"We couldn't find a `spark.theme.config` file in this folder. Please make sure that the file is located in the root folder of your project"
|
|
19
23
|
)
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
const configFilePath = join(process.cwd(), configFile)
|
|
22
27
|
const configFileIsInJS = configFile === 'spark.theme.config.js'
|
|
23
|
-
const filePath = join(process.cwd(), configFile)
|
|
24
28
|
|
|
25
29
|
const allowedExtensions = ['.ts', '.mts', '.cts', '.js', '.cjs', '.mjs']
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const jsFileExtension = '.js'
|
|
31
|
+
const configFileExtension = extname(configFilePath)
|
|
32
|
+
if (!allowedExtensions.includes(configFileExtension)) {
|
|
33
|
+
system.exit(`Your spark.theme.config file extension (${configFileExtension}) is not supported.`)
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
const
|
|
32
|
-
const jsCode = transformSync(
|
|
36
|
+
const configFileContent = readFileSync(configFilePath, 'utf-8')
|
|
37
|
+
const jsCode = transformSync(configFileContent, { loader: 'ts' }).code
|
|
33
38
|
|
|
34
|
-
const jsFilePath =
|
|
35
|
-
const jsFileContents =
|
|
39
|
+
const jsFilePath = configFilePath.replace(/\.ts$|\.mts$|\.cts$|\.mjs|\.cjs$/, jsFileExtension)
|
|
40
|
+
const jsFileContents = jsCode
|
|
36
41
|
|
|
37
42
|
if (!configFileIsInJS) writeFileSync(jsFilePath, jsFileContents)
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
44
|
+
import(jsFilePath)
|
|
45
|
+
.then(module => {
|
|
46
|
+
const { tailwindThemeConfigFilePath, CSSTokens } = module.default
|
|
47
|
+
|
|
48
|
+
createTailwindThemeConfigFile(tailwindThemeConfigFilePath)
|
|
49
|
+
createCSSTokensFile(CSSTokens.filePath, CSSTokens.themes)
|
|
50
|
+
|
|
51
|
+
const child = spawn(process.execPath, [jsFilePath], {
|
|
52
|
+
stdio: 'inherit',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
child.on('exit', code => {
|
|
56
|
+
if (!configFileIsInJS) unlinkSync(jsFilePath)
|
|
57
|
+
logger.success(
|
|
58
|
+
`✨ Your Spark Tailwind theme config file has been successfully created: ${join(
|
|
59
|
+
process.cwd(),
|
|
60
|
+
tailwindThemeConfigFilePath
|
|
61
|
+
)}`
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
logger.success(
|
|
65
|
+
`✨ Your Spark Tailwind CSS Tokens file file has been successfully created: ${join(
|
|
66
|
+
process.cwd(),
|
|
67
|
+
CSSTokens.filePath
|
|
68
|
+
)}`
|
|
69
|
+
)
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
71
|
+
process.exit(code)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
.catch(err => {
|
|
75
|
+
unlinkSync(jsFilePath)
|
|
76
|
+
system.exit(`
|
|
77
|
+
Something went wrong while running ${configFilePath}: ${err}`)
|
|
78
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Validator } from './Validator.mjs'
|
|
2
|
+
|
|
3
|
+
export class DescriptionValidator extends Validator {
|
|
4
|
+
constructor() {
|
|
5
|
+
super()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
validate(description) {
|
|
9
|
+
if (!description) {
|
|
10
|
+
return 'You package must have a description'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (description.length < 10) {
|
|
14
|
+
return 'Description is too short (minimum is 10 chars)'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { System } from '../core/index.mjs'
|
|
2
|
+
import { Validator } from './Validator.mjs'
|
|
3
|
+
|
|
4
|
+
export class NameValidator extends Validator {
|
|
5
|
+
system
|
|
6
|
+
|
|
7
|
+
constructor({ system }) {
|
|
8
|
+
super()
|
|
9
|
+
this.system = system
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
validate(name) {
|
|
13
|
+
if (!name) {
|
|
14
|
+
return 'Package name must me defined'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!/^[a-z-]*$/.test(name)) {
|
|
18
|
+
return 'Name name must contain letters and dash symbols only (ex: "my-package")'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (this.system.isPackageCreated(name)) {
|
|
22
|
+
return 'A package with that name already exists. Either delete it manually or use another name.'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spark-ui/cli-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Spark CLI utils",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@clack/prompts": "0.2.2",
|
|
16
|
+
"@spark-ui/theme-utils": "*",
|
|
17
|
+
"camel-case": "4.1.2",
|
|
16
18
|
"chalk": "5.2.0",
|
|
17
19
|
"commander": "10.0.0",
|
|
18
20
|
"esbuild": "0.17.8",
|
|
@@ -25,5 +27,5 @@
|
|
|
25
27
|
"url": "git@github.com:adevinta/spark.git",
|
|
26
28
|
"directory": "packages/utils/cli"
|
|
27
29
|
},
|
|
28
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "a7cfc5667178ab46c812a43423f57d10037a5c6a"
|
|
29
31
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pascalCase } from 'pascal-case'
|
|
2
2
|
|
|
3
|
-
export default ({
|
|
4
|
-
const componentName = pascalCase(
|
|
3
|
+
export default ({ name, description }) => {
|
|
4
|
+
const componentName = pascalCase(name)
|
|
5
5
|
|
|
6
6
|
return `import { ArgsTable, Meta, Story } from '@storybook/addon-docs'
|
|
7
7
|
import { ReactLiveBlock } from '@docs/helpers/ReactLiveBlock'
|
|
@@ -20,13 +20,13 @@ ${description}
|
|
|
20
20
|
<StoryHeading label="Install" />
|
|
21
21
|
|
|
22
22
|
\`\`\`
|
|
23
|
-
npm install @spark-ui/${
|
|
23
|
+
npm install @spark-ui/${name}
|
|
24
24
|
\`\`\`
|
|
25
25
|
|
|
26
26
|
<StoryHeading label="Import" />
|
|
27
27
|
|
|
28
28
|
\`\`\`
|
|
29
|
-
import { ${componentName} } from "@spark-ui/${
|
|
29
|
+
import { ${componentName} } from "@spark-ui/${name}"
|
|
30
30
|
\`\`\`
|
|
31
31
|
|
|
32
32
|
<StoryHeading label="Props" />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pascalCase } from 'pascal-case'
|
|
2
2
|
|
|
3
|
-
export default ({
|
|
4
|
-
const componentName = pascalCase(
|
|
3
|
+
export default ({ name, description }) => {
|
|
4
|
+
const componentName = pascalCase(name)
|
|
5
5
|
|
|
6
6
|
return `import { ReactLiveBlock } from '@docs/helpers/ReactLiveBlock'
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pascalCase } from 'pascal-case'
|
|
2
2
|
|
|
3
|
-
export default ({
|
|
4
|
-
const componentName = pascalCase(
|
|
3
|
+
export default ({ name }) => {
|
|
4
|
+
const componentName = pascalCase(name)
|
|
5
5
|
|
|
6
6
|
return `import { render, screen } from '@testing-library/react'
|
|
7
7
|
import userEvent from '@testing-library/user-event'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pascalCase } from 'pascal-case'
|
|
2
2
|
|
|
3
|
-
export default ({
|
|
4
|
-
const componentName = pascalCase(
|
|
3
|
+
export default ({ name }) => {
|
|
4
|
+
const componentName = pascalCase(name)
|
|
5
5
|
|
|
6
6
|
return `import { ComponentPropsWithoutRef, PropsWithChildren } from 'react'
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pascalCase } from 'pascal-case'
|
|
2
2
|
|
|
3
|
-
export default ({
|
|
4
|
-
const componentName = pascalCase(
|
|
3
|
+
export default ({ name }) => {
|
|
4
|
+
const componentName = pascalCase(name)
|
|
5
5
|
|
|
6
6
|
return `export { ${componentName} } from './${componentName}'
|
|
7
7
|
`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default ({ name, description }) => `{
|
|
2
|
+
"name": "@spark-ui/${name}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "${description}",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "vite build"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { camelCase } from 'camel-case'
|
|
2
|
+
|
|
3
|
+
export default ({ name, description }) => {
|
|
4
|
+
const hookName = camelCase(name)
|
|
5
|
+
|
|
6
|
+
return `import { ArgsTable, Meta, Story } from '@storybook/addon-docs'
|
|
7
|
+
import { ReactLiveBlock } from '@docs/helpers/ReactLiveBlock'
|
|
8
|
+
import { StoryHeading } from '@docs/helpers/StoryHeading'
|
|
9
|
+
|
|
10
|
+
import { ${hookName} } from '.'
|
|
11
|
+
|
|
12
|
+
import * as stories from './${hookName}.stories'
|
|
13
|
+
|
|
14
|
+
<Meta title="Hooks/${hookName}" />
|
|
15
|
+
|
|
16
|
+
# ${hookName}
|
|
17
|
+
|
|
18
|
+
${description}
|
|
19
|
+
|
|
20
|
+
<StoryHeading label="Install" />
|
|
21
|
+
|
|
22
|
+
\`\`\`
|
|
23
|
+
npm install @spark-ui/${name}
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
<StoryHeading label="Import" />
|
|
27
|
+
|
|
28
|
+
\`\`\`
|
|
29
|
+
import { ${hookName} } from "@spark-ui/${name}"
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
32
|
+
<StoryHeading label="Usage" />
|
|
33
|
+
|
|
34
|
+
<ArgsTable of={${hookName}} />
|
|
35
|
+
|
|
36
|
+
\`\`\`jsx
|
|
37
|
+
import { ${hookName} } from "@spark-ui/${name}"
|
|
38
|
+
|
|
39
|
+
const Demo = () => {}
|
|
40
|
+
\`\`\`
|
|
41
|
+
`
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { camelCase } from 'camel-case'
|
|
2
|
+
|
|
3
|
+
export default ({ name }) => {
|
|
4
|
+
const hookName = camelCase(name)
|
|
5
|
+
|
|
6
|
+
return `import { renderHook } from '@testing-library/react'
|
|
7
|
+
import { describe, expect, it } from 'vitest'
|
|
8
|
+
|
|
9
|
+
import { ${hookName} } from './${hookName}'
|
|
10
|
+
|
|
11
|
+
describe('${hookName}', () => {
|
|
12
|
+
it('should be defined', () => {
|
|
13
|
+
expect(${hookName}).toBeDefined()
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
`
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default () => 'src'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default ({ name, description }) => `{
|
|
2
|
+
"name": "@spark-ui/${name}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "${description}",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "vite build"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export default () => `import { terser } from 'rollup-plugin-terser'
|
|
2
|
+
import dts from 'vite-plugin-dts'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
build: {
|
|
6
|
+
target: 'es2015',
|
|
7
|
+
lib: {
|
|
8
|
+
entry: 'src/index.ts',
|
|
9
|
+
formats: ['es', 'cjs'],
|
|
10
|
+
fileName: 'index',
|
|
11
|
+
},
|
|
12
|
+
rollupOptions: {
|
|
13
|
+
external: ['node:path', 'node:fs'],
|
|
14
|
+
plugins: [terser()],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
plugins: [
|
|
18
|
+
dts({
|
|
19
|
+
entryRoot: './src',
|
|
20
|
+
}),
|
|
21
|
+
],
|
|
22
|
+
}
|
|
23
|
+
`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default () => ''
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { appendFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import hexRgb from 'hex-rgb'
|
|
5
|
+
|
|
6
|
+
import { isHex, isStringOrNumber, toKebabCase } from './utils.js'
|
|
7
|
+
|
|
8
|
+
function flattenTheme(theme, className) {
|
|
9
|
+
const flattenedTheme = {}
|
|
10
|
+
|
|
11
|
+
function flatten(obj, path) {
|
|
12
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
13
|
+
if (value !== null && typeof value === 'object') {
|
|
14
|
+
const formattedPath = path ? `--${path}-${key}` : `--${key}`
|
|
15
|
+
flatten(value, toKebabCase(formattedPath.replace(/-{3,}/, '--')))
|
|
16
|
+
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (isStringOrNumber(value)) {
|
|
21
|
+
const getFormattedValue = () => {
|
|
22
|
+
if (isHex(value)) {
|
|
23
|
+
const { red, green, blue } = hexRgb(value)
|
|
24
|
+
|
|
25
|
+
return `${red} ${green} ${blue}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return value
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
flattenedTheme[`${path}-${toKebabCase(key)}`] = getFormattedValue()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
flatten(theme)
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...flattenedTheme,
|
|
40
|
+
className,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const toStringifiedTheme = theme =>
|
|
45
|
+
Object.entries(theme)
|
|
46
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
47
|
+
.join(';')
|
|
48
|
+
|
|
49
|
+
const getStringifiedThemes = themeRecord =>
|
|
50
|
+
Object.keys(themeRecord).map(key => {
|
|
51
|
+
const { className, ...rest } = flattenTheme(themeRecord[key], key)
|
|
52
|
+
|
|
53
|
+
return key === 'default'
|
|
54
|
+
? `:root{${toStringifiedTheme(rest)}}`
|
|
55
|
+
: `.${className}{${toStringifiedTheme(rest)}}`
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a CSS file containing theme tokens represented as CSS custom properties
|
|
60
|
+
*
|
|
61
|
+
* @param {string} path - The file path where the CSS file will be created.
|
|
62
|
+
* @param {Record<string, Theme>} themeRecord - A record (with a required key of "default") of themes that will be included in the CSS Tokens file.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
*
|
|
66
|
+
* const defaultTheme: Theme = { ... }
|
|
67
|
+
* const darkTheme: Theme = { ... }
|
|
68
|
+
* const otherTheme: Theme = { ... }
|
|
69
|
+
*
|
|
70
|
+
* const themes = {
|
|
71
|
+
* default: defaultTheme,
|
|
72
|
+
* dark: darkTheme
|
|
73
|
+
* other: otherTheme
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* createCSSTokensFile('somePath.css', themes) // will generate a "somePath.css" file in the relative location from which it was called
|
|
77
|
+
*/
|
|
78
|
+
export function createCSSTokensFile(path, themeRecord) {
|
|
79
|
+
try {
|
|
80
|
+
appendFileSync(
|
|
81
|
+
join(process.cwd(), path),
|
|
82
|
+
`
|
|
83
|
+
@tailwind base;
|
|
84
|
+
@tailwind components;
|
|
85
|
+
@tailwind utilities;
|
|
86
|
+
@layer base {${getStringifiedThemes(themeRecord).join('')}}
|
|
87
|
+
`,
|
|
88
|
+
{
|
|
89
|
+
flag: 'w',
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Failed to create the CSS token file', error)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { defaultTheme } from '@spark-ui/theme-utils'
|
|
5
|
+
|
|
6
|
+
import { isHex, isStringOrNumber, toKebabCase, toKebabCaseKeys } from './utils.js'
|
|
7
|
+
|
|
8
|
+
function toTailwindConfig(theme) {
|
|
9
|
+
const themeCpy = JSON.parse(JSON.stringify(theme))
|
|
10
|
+
|
|
11
|
+
function flatten(obj, path) {
|
|
12
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
13
|
+
if (value !== null && typeof value === 'object') {
|
|
14
|
+
const formattedPath = path ? `--${path}-${key}` : `--${key}`
|
|
15
|
+
flatten(value, toKebabCase(formattedPath.replace(/-{3,}/, '--')))
|
|
16
|
+
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* eslint-disable */
|
|
21
|
+
if (isStringOrNumber(value)) {
|
|
22
|
+
const formattedPath =
|
|
23
|
+
/--colors/.test(path || '') && isHex(value)
|
|
24
|
+
? `rgb(var(${path}-${toKebabCase(key)}) / <alpha-value>)`
|
|
25
|
+
: `var(${path}-${toKebabCase(key)})`
|
|
26
|
+
|
|
27
|
+
/* @ts-ignore */
|
|
28
|
+
obj[key] = formattedPath
|
|
29
|
+
/* eslint-enable */
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
flatten(themeCpy)
|
|
35
|
+
|
|
36
|
+
return toKebabCaseKeys(themeCpy)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Tailwind config file that links the [theme options](https://tailwindcss.com/docs/theme#configuration-reference) provided by Tailwind with the CSS custom property values generated using the "createCSSTokensFile" function
|
|
41
|
+
*
|
|
42
|
+
* @param {string} path - The file path where the Tailwind config file will be created.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
*
|
|
46
|
+
* createTailwindThemeConfigFile('./tailwind.theme.js') // will generate a "tailwind.theme.js" in the relative location from which it was called
|
|
47
|
+
*/
|
|
48
|
+
export function createTailwindThemeConfigFile(path) {
|
|
49
|
+
try {
|
|
50
|
+
writeFileSync(
|
|
51
|
+
join(process.cwd(), path),
|
|
52
|
+
`module.exports = ${JSON.stringify(toTailwindConfig(defaultTheme))}`,
|
|
53
|
+
{
|
|
54
|
+
flag: 'w',
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to create the Tailwind theme config file', error)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function toKebabCase(v) {
|
|
2
|
+
return v.replace(/[A-Z]/g, e => `-${e.toLocaleLowerCase()}`)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function isHex(value) {
|
|
6
|
+
if (typeof value === 'number') {
|
|
7
|
+
return false
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const regexp = /^#[0-9a-fA-F]+$/
|
|
11
|
+
|
|
12
|
+
return regexp.test(value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isStringOrNumber(value) {
|
|
16
|
+
return typeof value === 'string' || typeof value === 'number'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
20
|
+
function toKebabCaseKeys(obj, level = 1) {
|
|
21
|
+
const result = {}
|
|
22
|
+
for (const key in obj) {
|
|
23
|
+
const value = typeof obj[key] === 'object' ? toKebabCaseKeys(obj[key], level + 1) : obj[key]
|
|
24
|
+
result[level > 1 ? key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() : key] = value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { toKebabCase, isHex, toKebabCaseKeys, isStringOrNumber }
|
package/utils.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
import fse from 'fs-extra'
|
|
5
|
-
|
|
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
|
-
}
|
|
12
|
-
|
|
13
|
-
export const showError = (msg, foreignProgram) => {
|
|
14
|
-
log.error(`✖ Error: ${msg}\n`)
|
|
15
|
-
process.exit(1)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const writeFile = ({ path, content }) =>
|
|
19
|
-
fse
|
|
20
|
-
.outputFile(path, content)
|
|
21
|
-
.then(() => log.info(`Created ${path}`))
|
|
22
|
-
.catch(() => showError(`Failed creating ${path}`))
|