@create-mastery/cli 0.1.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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ <div align=center>
2
+
3
+ # Create Mastery CLI
4
+
5
+ </div>
6
+
7
+ The official Create Mastery CLI, created for enhancing DX
8
+
9
+ Run `cm --help` for all available commands
package/biome.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": [
3
+ "../biome.json"
4
+ ],
5
+ "root": false
6
+ }
package/cli.ts ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { program } from 'commander'
4
+ import gradient from 'gradient-string'
5
+ import addComponent from './commands/add/component'
6
+ import addLanguage from './commands/add/language'
7
+ import { genSchema } from './commands/generate-schema'
8
+ import printScripts from './commands/scripts'
9
+ import printVersion from './commands/version'
10
+ import cliInfo from './package.json'
11
+ import { createMasteryASCIIArtBig } from './utils/arts'
12
+ import { execSync } from 'node:child_process'
13
+ import chalk from 'chalk'
14
+
15
+ const add = program.command('add').description('add a language or component')
16
+ const gen = program.command('gen').description('generate the dictionary schema')
17
+
18
+ program.version(cliInfo.version)
19
+ program.name('cm')
20
+
21
+ program.addHelpText(
22
+ 'beforeAll',
23
+ gradient([
24
+ '#8dc4ff',
25
+ '#bddaff',
26
+ ]).multiline(createMasteryASCIIArtBig)
27
+ )
28
+
29
+ // I know this may look silly, but it's for creating space between the prompt and the help message
30
+ program.addHelpText('afterAll', ' ')
31
+
32
+ program.description(cliInfo.description)
33
+
34
+ program
35
+ .command('version')
36
+ .description(
37
+ 'display the version of the Create Mastery website and some other useful information'
38
+ )
39
+ .option('-v, --verbose', 'verbose output of version command', false)
40
+ .action((options) => printVersion(options.verbose))
41
+
42
+ program
43
+ .command('scripts')
44
+ .description('display all the available scripts in package.json')
45
+ .action(() => printScripts())
46
+
47
+ program
48
+ .command('clone')
49
+ .description('clone the Create Mastery repo')
50
+ .argument('<destination>', 'the directory where the repo will be cloned')
51
+ .action((destination) => {
52
+ try {
53
+ execSync(
54
+ `git clone https://github.com/Create-Mastery/website.git ${destination}`
55
+ )
56
+
57
+ console.log(chalk.blue('repo cloned in:'), destination)
58
+ console.log(
59
+ chalk.blue('now run `npm install` to install all the dependencies')
60
+ )
61
+ } catch (err) {
62
+ console.error('an error has occurred:', err)
63
+ }
64
+ })
65
+
66
+ gen
67
+ .command('schema')
68
+ .description('generates the schema for the dictionaries')
69
+ .action(() => genSchema())
70
+
71
+ add
72
+ .command('component')
73
+ .description('creates a new component (in the src/app/components/ directory)')
74
+ .argument('<name>', 'component to add')
75
+ .option('-p, --props', 'the component is generated with props', false)
76
+ .option('-c, --client', 'the component is a client component', false)
77
+ .action((name, options) => addComponent(name, options.props, options.client))
78
+
79
+ add
80
+ .command('language')
81
+ .description('creates a new language for i18n')
82
+ .argument('<language>', 'language to add')
83
+ .action((language) => addLanguage(language))
84
+
85
+ program.parse()
@@ -0,0 +1,73 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import chalk from 'chalk'
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = path.dirname(__filename)
8
+ const componentsDir = path.resolve(__dirname, '../../../src/components/')
9
+
10
+ export default function addComponent(
11
+ componentName: string,
12
+ props: boolean,
13
+ client: boolean
14
+ ) {
15
+ const filePath = path.resolve(componentsDir, `${componentName}.tsx`)
16
+ const dirPath = path.dirname(filePath)
17
+
18
+ const rawName = componentName.trim()
19
+
20
+ if (!rawName) {
21
+ throw new Error('Component name cannot be empty')
22
+ }
23
+
24
+ const fileName = rawName.split('/').pop()
25
+
26
+ if (!fileName) {
27
+ throw new Error('Invalid component name')
28
+ }
29
+
30
+ const finalName = fileName
31
+ .replace(/[-_](\w)/g, (_, c) => c.toUpperCase())
32
+ .replace(/^\w/, (c) => c.toUpperCase())
33
+
34
+ let content: string = ''
35
+
36
+ if (fs.existsSync(filePath)) {
37
+ console.log(
38
+ chalk.red('The component'),
39
+ chalk.reset(`${rawName}.tsx`),
40
+ chalk.red('already exists')
41
+ )
42
+
43
+ return
44
+ }
45
+
46
+ if (client) {
47
+ content += `'use client'\n\n`
48
+ }
49
+
50
+ if (props) {
51
+ content += `type Props = {\n\n}\n\n`
52
+ }
53
+
54
+ content += `const ${finalName} = (${props ? '{ }: Props' : ''}) => {
55
+ return (
56
+ <div></div>
57
+ )
58
+ }
59
+
60
+ export default ${finalName}`
61
+
62
+ fs.mkdirSync(dirPath, {
63
+ recursive: true,
64
+ })
65
+ fs.writeFileSync(filePath, content)
66
+
67
+ console.log(
68
+ chalk.blue('Component created -'),
69
+ chalk.reset(`${componentName}.tsx`)
70
+ )
71
+ console.log(chalk.blue('Props -'), chalk.reset(props))
72
+ console.log(chalk.blue('Client -'), chalk.reset(client))
73
+ }
@@ -0,0 +1,126 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import chalk from 'chalk'
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = path.dirname(__filename)
8
+ const dictionarieDir = path.resolve(__dirname, '../../../src/i18n/dictionaries')
9
+
10
+ type Json =
11
+ | string
12
+ | number
13
+ | boolean
14
+ | null
15
+ | Json[]
16
+ | {
17
+ [key: string]: Json
18
+ }
19
+
20
+ function stripValues(obj: Json): Json {
21
+ if (obj === '$schema') return obj
22
+ if (typeof obj === 'string') return ''
23
+ if (Array.isArray(obj)) return obj.map(stripValues)
24
+ if (typeof obj === 'object' && obj !== null) {
25
+ return Object.fromEntries(
26
+ Object.entries(obj).map(([k, v]) => [
27
+ k,
28
+ k === '$schema' ? v : stripValues(v),
29
+ ])
30
+ ) as Json
31
+ }
32
+ return obj
33
+ }
34
+
35
+ function genLocales(files: string[]) {
36
+ const locales = files.map((f) => `'${f.replace('.json', '')}'`).join(' | ')
37
+ const content = `export type locales = ${locales}\n`
38
+
39
+ fs.writeFileSync(
40
+ path.resolve(__dirname, '../../../src/i18n/types/locales.ts'),
41
+ content
42
+ )
43
+ }
44
+
45
+ function genI18n(files: string[]) {
46
+ const locales = files
47
+ .map((f) => `'${f.replace('.json', '')}'`)
48
+ .join(',\n\t\t')
49
+ const content = `export const i18n = {
50
+ defaultLocale: 'en',
51
+ locales: [
52
+ \t\t${locales}
53
+ ],
54
+ }`
55
+
56
+ fs.writeFileSync(
57
+ path.resolve(__dirname, '../../../src/i18n/i18n.ts'),
58
+ content
59
+ )
60
+ }
61
+
62
+ function genDictionaryLoaders(files: string[]) {
63
+ const dictionaries = files.map((f) => f.replace('.json', ''))
64
+
65
+ let dictionariesLoaders: string = `import { type Locale } from '../config'\n\nexport const dictionariesLoaders: Locale = {\n`
66
+
67
+ for (const dictionary of dictionaries) {
68
+ dictionariesLoaders += `\t${dictionary}: () => import('./${dictionary}.json').then((module) => module.default),\n`
69
+ }
70
+
71
+ dictionariesLoaders += '}'
72
+
73
+ fs.writeFileSync(
74
+ path.resolve(
75
+ __dirname,
76
+ '../../../src/i18n/dictionaries/dictionaries-loaders.ts'
77
+ ),
78
+ dictionariesLoaders
79
+ )
80
+ }
81
+
82
+ export default function addLanguage(language: string) {
83
+ const input = JSON.parse(fs.readFileSync(`${dictionarieDir}/en.json`, 'utf8'))
84
+ const output = stripValues(input)
85
+
86
+ let files: string[]
87
+
88
+ files = fs
89
+ .readdirSync(dictionarieDir)
90
+ .filter((f) => f.endsWith('.json') && f !== 'schema.schema.json')
91
+
92
+ if (files.includes(`${language}.json`)) {
93
+ console.log(
94
+ chalk.red('This language:'),
95
+ chalk.reset(language),
96
+ chalk.red('already exists')
97
+ )
98
+
99
+ return
100
+ }
101
+
102
+ fs.writeFileSync(
103
+ `${dictionarieDir}/${language}.json`,
104
+ JSON.stringify(output, null, 2)
105
+ )
106
+
107
+ files = fs
108
+ .readdirSync(dictionarieDir)
109
+ .filter((f) => f.endsWith('.json') && f !== 'schema.schema.json')
110
+
111
+ genDictionaryLoaders(files)
112
+ genLocales(files)
113
+ genI18n(files)
114
+
115
+ console.log(chalk.blue('Language - '), chalk.reset(language))
116
+ console.log(
117
+ chalk.blue('File Create at - '),
118
+ chalk.reset(`@/i18n/dictionaries/${language}.json`)
119
+ )
120
+ console.log(chalk.blue('Regenerated files:'))
121
+ console.log(' @/src/i18n/i18n.ts')
122
+ console.log(' @/src/i18n/types/locales.ts')
123
+ console.log(' @/src/i18n/dictionaries/dictionaries-loaders.ts')
124
+ console.log()
125
+ console.log(chalk.blue('Now you need to add the actual values to the file!'))
126
+ }
@@ -0,0 +1,56 @@
1
+ import * as GenerateSchema from 'generate-schema'
2
+
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import chalk from 'chalk'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = path.dirname(__filename)
10
+ const dictionarieDir = path.resolve(__dirname, '../../src/i18n/dictionaries/')
11
+ const dictionary = JSON.parse(
12
+ fs.readFileSync(`${dictionarieDir}/en.json`, 'utf8')
13
+ )
14
+ const schemaPath = path.resolve(
15
+ __dirname,
16
+ `${dictionarieDir}/schema.schema.json`
17
+ )
18
+
19
+ interface JsonSchema {
20
+ type: string
21
+ properties?: {
22
+ [key: string]: JsonSchema
23
+ }
24
+ required?: string[]
25
+ }
26
+
27
+ function addRequiredRecursively(
28
+ schemaNode: JsonSchema,
29
+ dataNode: Record<string, unknown>
30
+ ) {
31
+ if (
32
+ schemaNode.type === 'object'
33
+ && dataNode
34
+ && typeof dataNode === 'object'
35
+ ) {
36
+ schemaNode.required = Object.keys(dataNode).filter((k) => k !== '$schema')
37
+ if (schemaNode.properties) {
38
+ for (const key of Object.keys(schemaNode.properties || {})) {
39
+ addRequiredRecursively(
40
+ schemaNode.properties[key],
41
+ dataNode[key] as Record<string, unknown>
42
+ )
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ export function genSchema() {
49
+ const schema = GenerateSchema.json('dictionary schema', dictionary)
50
+
51
+ addRequiredRecursively(schema, dictionary)
52
+
53
+ fs.writeFileSync(schemaPath, JSON.stringify(schema, null, 2), 'utf8')
54
+
55
+ console.log(chalk.blue('Schema generated successfully'))
56
+ }
@@ -0,0 +1,15 @@
1
+ import chalk from 'chalk'
2
+ import siteInfo from '../../package.json'
3
+ import { createMasteryASCIIArtSmall } from '../utils/arts'
4
+
5
+ export default function printScripts() {
6
+ console.log(chalk.blue(createMasteryASCIIArtSmall))
7
+ console.log(chalk.blue('Available scripts:'))
8
+ console.log(chalk.blue('──────────────────────────────────────────'))
9
+ Object.entries(siteInfo.scripts).forEach(([key, value]) => {
10
+ console.log(
11
+ `${chalk.blue(key.padEnd(10))} ${chalk.blue('─')} ${chalk.reset(value)}`
12
+ )
13
+ })
14
+ console.log()
15
+ }
@@ -0,0 +1,38 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import chalk from 'chalk'
5
+ import { globSync } from 'glob'
6
+ import siteInfo from '../../package.json'
7
+ import { createMasteryASCIIArtSmall } from '../utils/arts'
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = path.dirname(__filename)
11
+
12
+ export default function printVersion(verbose: boolean) {
13
+ const dictionarieDir = path.resolve(__dirname, '../../src/i18n/dictionaries')
14
+ const guideDirectory = path.resolve(__dirname, '../../src/app/**/guide')
15
+
16
+ // since schema.schema.json is also counted we decrement the value by 1 to get the actual number of languages
17
+ const getLanguages = () =>
18
+ fs.readdirSync(dictionarieDir).filter((f) => f.endsWith('.json')).length - 1
19
+
20
+ const deps = Object.keys(siteInfo.dependencies).length
21
+ const devDeps = Object.keys(siteInfo.devDependencies).length
22
+
23
+ const languages: number = getLanguages()
24
+ const guides: number = globSync(`${guideDirectory}/**/*.tsx`).length
25
+
26
+ console.log(chalk.blue(createMasteryASCIIArtSmall))
27
+ console.log(chalk.blue('VERSION ─'), chalk.reset(siteInfo.version))
28
+ console.log(chalk.blue('GUIDES ─'), chalk.reset(guides))
29
+ console.log(chalk.blue('LANGUAGES ─'), chalk.reset(languages))
30
+
31
+ if (verbose) {
32
+ console.log(chalk.blue('──────────────────────────────────────────'))
33
+ console.log(chalk.blue('DEPENDENCIES ─'), chalk.reset(deps))
34
+ console.log(chalk.blue('DEV DEPENDENCIES ─'), chalk.reset(devDeps))
35
+ }
36
+
37
+ console.log()
38
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@create-mastery/cli",
3
+ "version": "0.1.0",
4
+ "description": "The official Create Website CLI, created to enhance the DX",
5
+ "homepage": "https://github.com/Create-Mastery/website/tree/main/cli#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Create-Mastery/website.git#readme"
9
+ },
10
+ "license": "GPL-3.0-only",
11
+ "author": "Create Mastery",
12
+ "type": "module",
13
+ "main": "./cli.ts",
14
+ "bin": {
15
+ "cm": "cli.ts"
16
+ },
17
+ "dependencies": {
18
+ "gradient-string": "^3.0.0",
19
+ "chalk": "^5.6.2",
20
+ "commander": "^14.0.2",
21
+ "generate-schema": "^2.6.0",
22
+ "glob": "^13.0.0",
23
+ "tsx": "^4.21.0"
24
+ },
25
+ "devDependencies": {
26
+ "@biomejs/biome": "^2.3.11"
27
+ },
28
+ "keywords": [
29
+ "cli"
30
+ ]
31
+ }
package/utils/arts.ts ADDED
@@ -0,0 +1,14 @@
1
+ export const createMasteryASCIIArtSmall: string = `
2
+ ┏━╸┏━┓┏━╸┏━┓╺┳╸┏━╸ ┏┳┓┏━┓┏━┓╺┳╸┏━╸┏━┓╻ ╻
3
+ ┃ ┣┳┛┣╸ ┣━┫ ┃ ┣╸ ┃┃┃┣━┫┗━┓ ┃ ┣╸ ┣┳┛┗┳┛
4
+ ┗━╸╹┗╸┗━╸╹ ╹ ╹ ┗━╸ ╹ ╹╹ ╹┗━┛ ╹ ┗━╸╹┗╸ ╹
5
+ ──────────────────────────────────────────`
6
+
7
+ export const createMasteryASCIIArtBig: string = `
8
+ ██████╗██████╗ ███████╗ █████╗ ████████╗███████╗ ███╗ ███╗ █████╗ ███████╗████████╗███████╗██████╗ ██╗ ██╗
9
+ ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝ ████╗ ████║██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗╚██╗ ██╔╝
10
+ ██║ ██████╔╝█████╗ ███████║ ██║ █████╗ ██╔████╔██║███████║███████╗ ██║ █████╗ ██████╔╝ ╚████╔╝
11
+ ██║ ██╔══██╗██╔══╝ ██╔══██║ ██║ ██╔══╝ ██║╚██╔╝██║██╔══██║╚════██║ ██║ ██╔══╝ ██╔══██╗ ╚██╔╝
12
+ ╚██████╗██║ ██║███████╗██║ ██║ ██║ ███████╗ ██║ ╚═╝ ██║██║ ██║███████║ ██║ ███████╗██║ ██║ ██║
13
+ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
14
+ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────`