diff --git a/packages/utils/cli/bin/core/Logger.mjs b/packages/utils/cli/bin/core/Logger.mjs
new file mode 100644
index 000000000..4b13436eb
--- /dev/null
+++ b/packages/utils/cli/bin/core/Logger.mjs
@@ -0,0 +1,19 @@
+import chalk from 'chalk'
+
+export class Logger {
+ success(message) {
+ console.log(chalk.green(message))
+ }
+
+ error(message) {
+ console.log(chalk.red(message))
+ }
+
+ info(message) {
+ console.log(chalk.yellow(message))
+ }
+
+ warning(message) {
+ console.log(chalk.green(message))
+ }
+}
diff --git a/packages/utils/cli/bin/core/System.mjs b/packages/utils/cli/bin/core/System.mjs
new file mode 100644
index 000000000..e65ff2088
--- /dev/null
+++ b/packages/utils/cli/bin/core/System.mjs
@@ -0,0 +1,45 @@
+import fse from 'fs-extra'
+import glob from 'glob'
+
+export class System {
+ logger
+
+ constructor({ logger }) {
+ this.logger = logger
+ }
+
+ exit(message) {
+ this.logger.error(`✖ Error: ${message}\n`)
+ process.exit(1)
+ }
+
+ getBasePath() {
+ return process.cwd()
+ }
+
+ getPackageJSON() {
+ const basePath = this.getBasePath()
+
+ const raw = fse.readFileSync(`${basePath}/package.json`).toString()
+
+ return JSON.parse(raw)
+ }
+
+ isPackageCreated(name) {
+ const base = this.getBasePath()
+ const packageJSON = this.getPackageJSON()
+
+ return packageJSON.workspaces.some(workspace => {
+ const packages = glob.sync(`${base}/${workspace}/`)
+
+ return packages.some(path => path.endsWith(`/${name}/`))
+ })
+ }
+
+ writeFile({ path, content }) {
+ return fse
+ .outputFile(path, content)
+ .then(() => this.logger.info(`Created ${path}`))
+ .catch(error => this.exit(`Failed creating ${path}`))
+ }
+}
diff --git a/packages/utils/cli/bin/core/index.mjs b/packages/utils/cli/bin/core/index.mjs
new file mode 100644
index 000000000..27151886e
--- /dev/null
+++ b/packages/utils/cli/bin/core/index.mjs
@@ -0,0 +1,2 @@
+export { Logger } from './Logger.mjs'
+export { System } from './System.mjs'
diff --git a/packages/utils/cli/bin/generators/Generator.mjs b/packages/utils/cli/bin/generators/Generator.mjs
new file mode 100644
index 000000000..0f5351cd7
--- /dev/null
+++ b/packages/utils/cli/bin/generators/Generator.mjs
@@ -0,0 +1,5 @@
+export class Generator {
+ execute() {
+ throw new Error('execute method should be implemented')
+ }
+}
diff --git a/packages/utils/cli/bin/generators/TemplateGenerator.mjs b/packages/utils/cli/bin/generators/TemplateGenerator.mjs
new file mode 100644
index 000000000..e6de8f942
--- /dev/null
+++ b/packages/utils/cli/bin/generators/TemplateGenerator.mjs
@@ -0,0 +1,83 @@
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import glob from 'glob'
+import { pascalCase } from 'pascal-case'
+import { camelCase } from 'camel-case'
+
+import { System } from '../core/index.mjs'
+import { Generator } from './Generator.mjs'
+
+export class TemplateGenerator extends Generator {
+ static TYPES = {
+ COMPONENT: 'component',
+ HOOK: 'hook',
+ UTIL: 'util',
+ }
+
+ static CONTEXTS = {
+ [TemplateGenerator.TYPES.COMPONENT]: 'components',
+ [TemplateGenerator.TYPES.HOOK]: 'components',
+ [TemplateGenerator.TYPES.UTIL]: 'utils',
+ }
+
+ constructor({ system }) {
+ super()
+ this.system = system
+ }
+
+ getDest({ type, name }) {
+ const basePath = this.system.getBasePath()
+ const context = TemplateGenerator.CONTEXTS[type]
+
+ return `${basePath}/packages/${context}/${name}`
+ }
+
+ getTemplatePaths({ type }) {
+ const pattern = fileURLToPath(new URL(`../../templates/${type}/**/*.js`, import.meta.url))
+
+ return new Promise((resolve, reject) => {
+ glob(pattern, async (error, paths) => {
+ if (error) {
+ return reject(error)
+ }
+
+ resolve(paths)
+ })
+ })
+ }
+
+ getTemplatePath({ path, name, type, dest }) {
+ const parsed = path
+ .replace(/(.*)\/templates\/([a-z-]+)\//, `${dest}/`)
+ .replaceAll(/\[|\]|\.js$/g, '')
+
+ if (type === TemplateGenerator.TYPES.COMPONENT) {
+ return parsed.replace('Component', pascalCase(name))
+ }
+
+ if (type === TemplateGenerator.TYPES.HOOK) {
+ return parsed.replace('name', camelCase(name))
+ }
+
+ return parsed
+ }
+
+ async execute({ type, name, description }) {
+ const dest = this.getDest({ type, name })
+ const paths = await this.getTemplatePaths({ type })
+
+ const promises = paths.map(path =>
+ import(path).then(module => ({
+ path: this.getTemplatePath({ path, name, type, dest }),
+ content: module.default({
+ name,
+ description,
+ }),
+ }))
+ )
+
+ const files = await Promise.all(promises)
+
+ return Promise.all(files.map(file => this.system.writeFile(file)))
+ }
+}
diff --git a/packages/utils/cli/bin/generators/index.mjs b/packages/utils/cli/bin/generators/index.mjs
new file mode 100644
index 000000000..ce7a2b23d
--- /dev/null
+++ b/packages/utils/cli/bin/generators/index.mjs
@@ -0,0 +1,2 @@
+export { Generator } from './Generator.mjs'
+export { TemplateGenerator } from './TemplateGenerator.mjs'
diff --git a/packages/utils/cli/bin/spark-generate.mjs b/packages/utils/cli/bin/spark-generate.mjs
index 0f6d7f239..4984f1eaf 100755
--- a/packages/utils/cli/bin/spark-generate.mjs
+++ b/packages/utils/cli/bin/spark-generate.mjs
@@ -1,156 +1,71 @@
#!/usr/bin/env node
-import chalk from 'chalk'
-import fse from 'fs-extra'
import * as prompt from '@clack/prompts'
-import { fileURLToPath } from 'node:url'
-import glob from 'glob'
-import { pascalCase } from 'pascal-case'
-import { log, showError, writeFile } from '../utils.js'
-const BASE_DIR = process.cwd()
-const rawRootPackageJSON = fse.readFileSync(`${BASE_DIR}/package.json`)
-let rootPackageJSON = JSON.parse(rawRootPackageJSON)
+import { TemplateGenerator } from './generators/index.mjs'
+import { Logger, System } from './core/index.mjs'
+import { DescriptionValidator, NameValidator } from './validators/index.mjs'
-const TEMPLATE_TYPE = {
- COMPONENT: 'component',
- HOOK: 'hook',
-}
-
-const WORKSPACES = {
- [TEMPLATE_TYPE.COMPONENT]: '/packages/components',
- [TEMPLATE_TYPE.HOOK]: '/packages/hooks',
-}
-
-const ERRORS = {
- ABORT: 'Aborted package generation',
- NO_PKG_NAME: 'Package name must me defined',
- INVALID_PKG_NAME: 'Name name must contain letters and dash symbols only (ex: "my-package")',
- INVALID_DESCRIPTION: 'Description is too short (minimum is 10 chars)',
- PKG_ALREADY_EXISTS:
- 'A package with that name already exists. Either delete it manually or use another name.',
-}
-
-const packageUtils = {
- /** Validate the format of the package name (kebab case format) */
- hasValidName: name => /^[a-z-]*$/.test(name),
- /** Check that a package of the same name does not exists across all workspaces */
- alreadyExists: name => {
- return rootPackageJSON.workspaces.some(workspace => {
- const existingPackages = glob.sync(`${BASE_DIR}/${workspace}/`)
- return existingPackages.some(path => path.endsWith(`/${name}/`))
- })
- },
- /** Retrieves the target folder of the generated package */
- getDirectory: (name, template) => `${WORKSPACES[template]}/${name}/`,
- /** Retrieves the full path to the folder of the generated package */
- getFullPath: (name, template) => `${BASE_DIR}${packageUtils.getDirectory(name, template)}`,
-}
+const logger = new Logger()
+const system = new System({ logger })
+const generator = new TemplateGenerator({ system })
-async function promptPackageName() {
+export const run = async () => {
const name = await prompt.text({
message: 'Package name (must contain letters and dash symbols only):',
initialValue: '',
validate(value) {
- if (value == null) return ERRORS.NO_PKG_NAME
- if (!packageUtils.hasValidName(value)) return ERRORS.INVALID_PKG_NAME
- if (packageUtils.alreadyExists(value)) return ERRORS.PKG_ALREADY_EXISTS
+ const validator = new NameValidator({ system })
+
+ return validator.validate(value)
},
})
- if (prompt.isCancel(name)) showError(ERRORS.ABORT)
-
- return name
-}
+ if (prompt.isCancel(name)) {
+ system.exit('Aborted package generation')
+ }
-async function promptPackageTemplate() {
- const template = await prompt.select({
+ const type = await prompt.select({
message: 'Chose a template:',
- initialValue: TEMPLATE_TYPE.COMPONENT,
+ initialValue: 'component',
options: [
{
- value: TEMPLATE_TYPE.COMPONENT,
+ value: TemplateGenerator.TYPES.COMPONENT,
label: 'Component',
- hint: 'Typescript dummy component with some tests, stories and config files',
+ hint: 'TypeScript component package',
},
{
- value: TEMPLATE_TYPE.HOOK,
+ value: TemplateGenerator.TYPES.HOOK,
label: 'Hook',
- hint: 'Typescript hook with some tests, stories and config files',
+ hint: 'TypeScript hook package',
+ },
+ {
+ value: TemplateGenerator.TYPES.UTIL,
+ label: 'Utility',
+ hint: 'TypeScript utility package',
},
],
})
- if (prompt.isCancel(template)) showError(ERRORS.ABORT)
-
- return template
-}
+ if (prompt.isCancel(type)) {
+ system.exit('Aborted package generation')
+ }
-async function promptPackageDescription() {
const description = await prompt.text({
message: 'Describe your package (short description):',
initialValue: '',
validate(value) {
- if (!value) return `You package must have a description`
- if (value.length < 10) return ERRORS.INVALID_DESCRIPTION
+ const validator = new DescriptionValidator()
+
+ return validator.validate(value)
},
})
- if (prompt.isCancel(description)) showError(ERRORS.ABORT)
-
- return description
-}
-
-/**
- * Program starts here
- */
-prompt.intro(`Generate Spark package`)
-
-const name = await promptPackageName()
-const template = await promptPackageTemplate()
-const description = await promptPackageDescription()
-
-const packagePath = packageUtils.getFullPath(name, template)
-
-switch (template) {
- case TEMPLATE_TYPE.COMPONENT:
- generateComponentPackage(name, description)
- break
- case TEMPLATE_TYPE.HOOK:
- generateHookPackage(name, description)
- break
-}
-
-prompt.outro(`Generating package...`)
-
-function generateComponentPackage(name, description) {
- const templatesPattern = fileURLToPath(new URL('../templates/**/*.js', import.meta.url))
-
- glob(templatesPattern, async (err, res) => {
- if (err) showError(err)
- if (res) {
- const templateContents = res.map(templatePath =>
- import(templatePath).then(module => ({
- path: templatePath
- .replace(/(.*)\/templates\//, packagePath)
- .replace('Component', pascalCase(name))
- .replaceAll(/\[|\]|\.js$/g, ''),
- content: module.default({
- component: name,
- description: description,
- }),
- }))
- )
+ if (prompt.isCancel(description)) {
+ system.exit('Aborted package generation')
+ }
- const filesToWrite = await Promise.all(templateContents)
-
- Promise.all(filesToWrite.map(writeFile)).then(() => {
- log.success('All package files has been properly written!')
- })
- }
- })
+ generator.execute({ name, type, description })
}
-function generateHookPackage(name, description) {
- showError('Todo: template for hook packages is not ready yet.')
-}
+run()
diff --git a/packages/utils/cli/bin/spark-setup-theme.mjs b/packages/utils/cli/bin/spark-setup-theme.mjs
index 8928b0bd8..267728977 100755
--- a/packages/utils/cli/bin/spark-setup-theme.mjs
+++ b/packages/utils/cli/bin/spark-setup-theme.mjs
@@ -5,7 +5,8 @@ import { join, extname, parse, sep } from 'path'
import { readFileSync, readdirSync, writeFileSync, unlinkSync } from 'fs'
import { transformSync } from 'esbuild'
-import { log, showError } from '../utils.js'
+const logger = new Logger()
+const system = new System({ logger })
const jsFileExtension = '.js'
@@ -14,7 +15,7 @@ const configFile = readdirSync(process.cwd()).find(fileName =>
)
if (!configFile) {
- showError(
+ system.exit(
"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"
)
}
@@ -24,8 +25,9 @@ const filePath = join(process.cwd(), configFile)
const allowedExtensions = ['.ts', '.mts', '.cts', '.js', '.cjs', '.mjs']
const fileExtension = extname(filePath)
+
if (!allowedExtensions.includes(fileExtension)) {
- showError(`Your spark.theme.config file extension (${fileExtension}) is not supported.`)
+ system.exit(`Your spark.theme.config file extension (${fileExtension}) is not supported.`)
}
const tsCode = readFileSync(filePath, 'utf-8')
@@ -42,6 +44,6 @@ const child = spawn(process.execPath, [jsFilePath], {
child.on('exit', code => {
if (!configFileIsInJS) unlinkSync(jsFilePath)
- log.success('✨ Your Spark theme config files have been successfully created!')
+ logger.success('✨ Your Spark theme config files have been successfully created!')
process.exit(code)
})
diff --git a/packages/utils/cli/bin/validators/DescriptionValidator.mjs b/packages/utils/cli/bin/validators/DescriptionValidator.mjs
new file mode 100644
index 000000000..851d837a9
--- /dev/null
+++ b/packages/utils/cli/bin/validators/DescriptionValidator.mjs
@@ -0,0 +1,19 @@
+import { Validator } from './Validator.mjs'
+
+export class DescriptionValidator extends Validator {
+ constructor() {
+ super()
+ }
+
+ validate(description) {
+ if (!description) {
+ return 'You package must have a description'
+ }
+
+ if (description.length < 10) {
+ return 'Description is too short (minimum is 10 chars)'
+ }
+
+ return undefined
+ }
+}
diff --git a/packages/utils/cli/bin/validators/NameValidator.mjs b/packages/utils/cli/bin/validators/NameValidator.mjs
new file mode 100644
index 000000000..64e4e878e
--- /dev/null
+++ b/packages/utils/cli/bin/validators/NameValidator.mjs
@@ -0,0 +1,25 @@
+import { System } from '../core/index.mjs'
+import { Validator } from './Validator.mjs'
+
+export class NameValidator extends Validator {
+ system
+
+ constructor({ system }) {
+ super()
+ this.system = system
+ }
+
+ validate(name) {
+ if (!name) {
+ return 'Package name must me defined'
+ }
+
+ if (!/^[a-z-]*$/.test(name)) {
+ return 'Name name must contain letters and dash symbols only (ex: "my-package")'
+ }
+
+ if (this.system.isPackageCreated(name)) {
+ return 'A package with that name already exists. Either delete it manually or use another name.'
+ }
+ }
+}
diff --git a/packages/utils/cli/bin/validators/Validator.mjs b/packages/utils/cli/bin/validators/Validator.mjs
new file mode 100644
index 000000000..b4de9ed83
--- /dev/null
+++ b/packages/utils/cli/bin/validators/Validator.mjs
@@ -0,0 +1,5 @@
+export class Validator {
+ validate() {
+ throw new Error('validate method should be implemented')
+ }
+}
diff --git a/packages/utils/cli/bin/validators/index.mjs b/packages/utils/cli/bin/validators/index.mjs
new file mode 100644
index 000000000..3a5e0737b
--- /dev/null
+++ b/packages/utils/cli/bin/validators/index.mjs
@@ -0,0 +1,2 @@
+export { NameValidator } from './NameValidator.mjs'
+export { DescriptionValidator } from './DescriptionValidator.mjs'
diff --git a/packages/utils/cli/package.json b/packages/utils/cli/package.json
index 2fde06b02..10a8ca08b 100644
--- a/packages/utils/cli/package.json
+++ b/packages/utils/cli/package.json
@@ -18,11 +18,12 @@
"esbuild": "0.17.8",
"fs-extra": "11.1.0",
"glob": "8.1.0",
- "pascal-case": "3.1.2"
+ "pascal-case": "3.1.2",
+ "camel-case": "4.1.2"
},
"repository": {
"type": "git",
"url": "git@github.com:adevinta/spark.git",
"directory": "packages/utils/cli"
}
-}
+}
\ No newline at end of file
diff --git a/packages/utils/cli/templates/[.npmignore].js b/packages/utils/cli/templates/component/[.npmignore].js
similarity index 100%
rename from packages/utils/cli/templates/[.npmignore].js
rename to packages/utils/cli/templates/component/[.npmignore].js
diff --git a/packages/utils/cli/templates/[package.json].js b/packages/utils/cli/templates/component/[package.json].js
similarity index 74%
rename from packages/utils/cli/templates/[package.json].js
rename to packages/utils/cli/templates/component/[package.json].js
index aa90cb490..14a4ad45f 100644
--- a/packages/utils/cli/templates/[package.json].js
+++ b/packages/utils/cli/templates/component/[package.json].js
@@ -1,5 +1,5 @@
-export default ({ component, description }) => `{
- "name": "@spark-ui/${component}",
+export default ({ name, description }) => `{
+ "name": "@spark-ui/${name}",
"version": "1.0.0",
"description": "${description}",
"publishConfig": {
diff --git a/packages/utils/cli/templates/[tsconfig.json].js b/packages/utils/cli/templates/component/[tsconfig.json].js
similarity index 100%
rename from packages/utils/cli/templates/[tsconfig.json].js
rename to packages/utils/cli/templates/component/[tsconfig.json].js
diff --git a/packages/utils/cli/templates/[vite.config.ts].js b/packages/utils/cli/templates/component/[vite.config.ts].js
similarity index 100%
rename from packages/utils/cli/templates/[vite.config.ts].js
rename to packages/utils/cli/templates/component/[vite.config.ts].js
diff --git a/packages/utils/cli/templates/src/[Component.stories.mdx].js b/packages/utils/cli/templates/component/src/[Component.stories.mdx].js
similarity index 78%
rename from packages/utils/cli/templates/src/[Component.stories.mdx].js
rename to packages/utils/cli/templates/component/src/[Component.stories.mdx].js
index 34bbf7f6a..799cb7c6b 100644
--- a/packages/utils/cli/templates/src/[Component.stories.mdx].js
+++ b/packages/utils/cli/templates/component/src/[Component.stories.mdx].js
@@ -1,7 +1,7 @@
import { pascalCase } from 'pascal-case'
-export default ({ component, description }) => {
- const componentName = pascalCase(component)
+export default ({ name, description }) => {
+ const componentName = pascalCase(name)
return `import { ArgsTable, Meta, Story } from '@storybook/addon-docs'
import { ReactLiveBlock } from '@docs/helpers/ReactLiveBlock'
@@ -20,13 +20,13 @@ ${description}
\`\`\`
-npm install @spark-ui/${component}
+npm install @spark-ui/${name}
\`\`\`
\`\`\`
-import { ${componentName} } from "@spark-ui/${component}"
+import { ${componentName} } from "@spark-ui/${name}"
\`\`\`
diff --git a/packages/utils/cli/templates/src/[Component.stories.tsx].js b/packages/utils/cli/templates/component/src/[Component.stories.tsx].js
similarity index 76%
rename from packages/utils/cli/templates/src/[Component.stories.tsx].js
rename to packages/utils/cli/templates/component/src/[Component.stories.tsx].js
index 164ae63ea..4987d6517 100644
--- a/packages/utils/cli/templates/src/[Component.stories.tsx].js
+++ b/packages/utils/cli/templates/component/src/[Component.stories.tsx].js
@@ -1,7 +1,7 @@
import { pascalCase } from 'pascal-case'
-export default ({ component, description }) => {
- const componentName = pascalCase(component)
+export default ({ name, description }) => {
+ const componentName = pascalCase(name)
return `import { ReactLiveBlock } from '@docs/helpers/ReactLiveBlock'
diff --git a/packages/utils/cli/templates/src/[Component.test.tsx].js b/packages/utils/cli/templates/component/src/[Component.test.tsx].js
similarity index 90%
rename from packages/utils/cli/templates/src/[Component.test.tsx].js
rename to packages/utils/cli/templates/component/src/[Component.test.tsx].js
index de96fa879..d25000ebb 100644
--- a/packages/utils/cli/templates/src/[Component.test.tsx].js
+++ b/packages/utils/cli/templates/component/src/[Component.test.tsx].js
@@ -1,7 +1,7 @@
import { pascalCase } from 'pascal-case'
-export default ({ component }) => {
- const componentName = pascalCase(component)
+export default ({ name }) => {
+ const componentName = pascalCase(name)
return `import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
diff --git a/packages/utils/cli/templates/src/[Component.tsx].js b/packages/utils/cli/templates/component/src/[Component.tsx].js
similarity index 79%
rename from packages/utils/cli/templates/src/[Component.tsx].js
rename to packages/utils/cli/templates/component/src/[Component.tsx].js
index 05a6710b6..be53c6ba5 100644
--- a/packages/utils/cli/templates/src/[Component.tsx].js
+++ b/packages/utils/cli/templates/component/src/[Component.tsx].js
@@ -1,7 +1,7 @@
import { pascalCase } from 'pascal-case'
-export default ({ component }) => {
- const componentName = pascalCase(component)
+export default ({ name }) => {
+ const componentName = pascalCase(name)
return `import { ComponentPropsWithoutRef, PropsWithChildren } from 'react'
diff --git a/packages/utils/cli/templates/component/src/[Component.variants.tsx].js b/packages/utils/cli/templates/component/src/[Component.variants.tsx].js
new file mode 100644
index 000000000..a79a5062e
--- /dev/null
+++ b/packages/utils/cli/templates/component/src/[Component.variants.tsx].js
@@ -0,0 +1,8 @@
+import { pascalCase } from 'pascal-case'
+
+export default ({ name }) => {
+ const componentName = pascalCase(name)
+
+ return `
+`
+}
diff --git a/packages/utils/cli/templates/src/[index.ts].js b/packages/utils/cli/templates/component/src/[index.ts].js
similarity index 57%
rename from packages/utils/cli/templates/src/[index.ts].js
rename to packages/utils/cli/templates/component/src/[index.ts].js
index 13dcf8788..a86a7d2c1 100644
--- a/packages/utils/cli/templates/src/[index.ts].js
+++ b/packages/utils/cli/templates/component/src/[index.ts].js
@@ -1,7 +1,7 @@
import { pascalCase } from 'pascal-case'
-export default ({ component }) => {
- const componentName = pascalCase(component)
+export default ({ name }) => {
+ const componentName = pascalCase(name)
return `export { ${componentName} } from './${componentName}'
`
diff --git a/packages/utils/cli/templates/hook/[.npmignore].js b/packages/utils/cli/templates/hook/[.npmignore].js
new file mode 100644
index 000000000..5446b8c3b
--- /dev/null
+++ b/packages/utils/cli/templates/hook/[.npmignore].js
@@ -0,0 +1,3 @@
+export default () => `src
+**/*.stories.*
+`
diff --git a/packages/utils/cli/templates/hook/[package.json].js b/packages/utils/cli/templates/hook/[package.json].js
new file mode 100644
index 000000000..14a4ad45f
--- /dev/null
+++ b/packages/utils/cli/templates/hook/[package.json].js
@@ -0,0 +1,15 @@
+export default ({ name, description }) => `{
+ "name": "@spark-ui/${name}",
+ "version": "1.0.0",
+ "description": "${description}",
+ "publishConfig": {
+ "access": "public"
+ },
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "vite build"
+ }
+}
+`
diff --git a/packages/utils/cli/templates/hook/[tsconfig.json].js b/packages/utils/cli/templates/hook/[tsconfig.json].js
new file mode 100644
index 000000000..dffccabfb
--- /dev/null
+++ b/packages/utils/cli/templates/hook/[tsconfig.json].js
@@ -0,0 +1,4 @@
+export default () => `{
+ "extends": "../../../tsconfig.json",
+ "include": ["src/**/*", "../../../global.d.ts"]
+}`
diff --git a/packages/utils/cli/templates/hook/[vite.config.ts].js b/packages/utils/cli/templates/hook/[vite.config.ts].js
new file mode 100644
index 000000000..e1378874c
--- /dev/null
+++ b/packages/utils/cli/templates/hook/[vite.config.ts].js
@@ -0,0 +1,7 @@
+export default () => `import path from 'path'
+import { getComponentConfiguration } from '../../../config/index'
+
+const { name } = require(path.resolve(__dirname, 'package.json'))
+
+export default getComponentConfiguration(name)
+`
diff --git a/packages/utils/cli/templates/hook/src/[index.ts].js b/packages/utils/cli/templates/hook/src/[index.ts].js
new file mode 100644
index 000000000..bcb4b0415
--- /dev/null
+++ b/packages/utils/cli/templates/hook/src/[index.ts].js
@@ -0,0 +1,8 @@
+import { camelCase } from 'camel-case'
+
+export default ({ name }) => {
+ const hookName = camelCase(name)
+
+ return `export { ${hookName} } from './${hookName}'
+`
+}
diff --git a/packages/utils/cli/templates/hook/src/[name.stories.mdx].js b/packages/utils/cli/templates/hook/src/[name.stories.mdx].js
new file mode 100644
index 000000000..10fac8de3
--- /dev/null
+++ b/packages/utils/cli/templates/hook/src/[name.stories.mdx].js
@@ -0,0 +1,42 @@
+import { camelCase } from 'camel-case'
+
+export default ({ name, description }) => {
+ const hookName = camelCase(name)
+
+ return `import { ArgsTable, Meta, Story } from '@storybook/addon-docs'
+import { ReactLiveBlock } from '@docs/helpers/ReactLiveBlock'
+import { StoryHeading } from '@docs/helpers/StoryHeading'
+
+import { ${hookName} } from '.'
+
+import * as stories from './${hookName}.stories'
+
+
+
+# ${hookName}
+
+${description}
+
+
+
+\`\`\`
+npm install @spark-ui/${name}
+\`\`\`
+
+
+
+\`\`\`
+import { ${hookName} } from "@spark-ui/${name}"
+\`\`\`
+
+
+
+
+
+\`\`\`jsx
+import { ${hookName} } from "@spark-ui/${name}"
+
+const Demo = () => {}
+\`\`\`
+`
+}
diff --git a/packages/utils/cli/templates/hook/src/[name.test.tsx].js b/packages/utils/cli/templates/hook/src/[name.test.tsx].js
new file mode 100644
index 000000000..920d64e80
--- /dev/null
+++ b/packages/utils/cli/templates/hook/src/[name.test.tsx].js
@@ -0,0 +1,17 @@
+import { camelCase } from 'camel-case'
+
+export default ({ name }) => {
+ const hookName = camelCase(name)
+
+ return `import { renderHook } from '@testing-library/react'
+import { describe, expect, it } from 'vitest'
+
+import { ${hookName} } from './${hookName}'
+
+describe('${hookName}', () => {
+ it('should be defined', () => {
+ expect(${hookName}).toBeDefined()
+ })
+})
+`
+}
diff --git a/packages/utils/cli/templates/hook/src/[name.tsx].js b/packages/utils/cli/templates/hook/src/[name.tsx].js
new file mode 100644
index 000000000..bf0ba8051
--- /dev/null
+++ b/packages/utils/cli/templates/hook/src/[name.tsx].js
@@ -0,0 +1,10 @@
+import { camelCase } from 'camel-case'
+
+export default ({ name }) => {
+ const hookName = camelCase(name)
+
+ return `export function ${hookName}() {
+ return null
+}
+`
+}
diff --git a/packages/utils/cli/templates/src/[Component.variants.tsx].js b/packages/utils/cli/templates/src/[Component.variants.tsx].js
deleted file mode 100644
index 1ccbfb880..000000000
--- a/packages/utils/cli/templates/src/[Component.variants.tsx].js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { pascalCase } from 'pascal-case'
-
-export default ({ component }) => {
- const componentName = pascalCase(component)
-
- return `
-`
-}
diff --git a/packages/utils/cli/templates/util/[.npmignore].js b/packages/utils/cli/templates/util/[.npmignore].js
new file mode 100644
index 000000000..b71019159
--- /dev/null
+++ b/packages/utils/cli/templates/util/[.npmignore].js
@@ -0,0 +1 @@
+export default () => 'src'
diff --git a/packages/utils/cli/templates/util/[package.json].js b/packages/utils/cli/templates/util/[package.json].js
new file mode 100644
index 000000000..14a4ad45f
--- /dev/null
+++ b/packages/utils/cli/templates/util/[package.json].js
@@ -0,0 +1,15 @@
+export default ({ name, description }) => `{
+ "name": "@spark-ui/${name}",
+ "version": "1.0.0",
+ "description": "${description}",
+ "publishConfig": {
+ "access": "public"
+ },
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "vite build"
+ }
+}
+`
diff --git a/packages/utils/cli/templates/util/[tsconfig.json].js b/packages/utils/cli/templates/util/[tsconfig.json].js
new file mode 100644
index 000000000..7e69de2d5
--- /dev/null
+++ b/packages/utils/cli/templates/util/[tsconfig.json].js
@@ -0,0 +1,5 @@
+export default () => `{
+ "extends": "../../../tsconfig.json",
+ "include": ["src/**/*", "../../global.d.ts"]
+}
+`
diff --git a/packages/utils/cli/templates/util/[vite.config.ts].js b/packages/utils/cli/templates/util/[vite.config.ts].js
new file mode 100644
index 000000000..d5ea037d4
--- /dev/null
+++ b/packages/utils/cli/templates/util/[vite.config.ts].js
@@ -0,0 +1,23 @@
+export default () => `import { terser } from 'rollup-plugin-terser'
+import dts from 'vite-plugin-dts'
+
+export default {
+ build: {
+ target: 'es2015',
+ lib: {
+ entry: 'src/index.ts',
+ formats: ['es', 'cjs'],
+ fileName: 'index',
+ },
+ rollupOptions: {
+ external: ['node:path', 'node:fs'],
+ plugins: [terser()],
+ },
+ },
+ plugins: [
+ dts({
+ entryRoot: './src',
+ }),
+ ],
+}
+`
diff --git a/packages/utils/cli/templates/util/src/[index.ts].js b/packages/utils/cli/templates/util/src/[index.ts].js
new file mode 100644
index 000000000..85cc77b58
--- /dev/null
+++ b/packages/utils/cli/templates/util/src/[index.ts].js
@@ -0,0 +1 @@
+export default () => ''