@brickflow/cli 0.0.5 → 0.0.7

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @brickflow/cli
2
2
 
3
+ ## 0.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - CLI command base
8
+
9
+ ## 0.0.6
10
+
11
+ ### Patch Changes
12
+
13
+ - -
14
+
3
15
  ## 0.0.5
4
16
 
5
17
  ### Patch Changes
package/index.mjs CHANGED
@@ -1,109 +1,60 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execFile } from 'node:child_process'
4
- import { readdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { access, readdir } from 'node:fs/promises'
5
4
  import path from 'node:path'
6
- import { fileURLToPath } from 'node:url'
7
- import { promisify } from 'node:util'
5
+ import { fileURLToPath, pathToFileURL } from 'node:url'
8
6
 
9
- const execFileAsync = promisify(execFile)
10
7
  const cliDir = path.dirname(fileURLToPath(import.meta.url))
11
- const repoRoot = path.resolve(cliDir, '../..')
12
- const workspaceRoots = ['apps', 'packages']
8
+ const commandsDir = path.join(cliDir, 'src')
13
9
 
14
- async function getWorkspacePackageJsonPaths() {
15
- const packagePaths = []
10
+ async function getAvailableCommands() {
11
+ const entries = await readdir(commandsDir, { withFileTypes: true })
16
12
 
17
- for (const workspaceRoot of workspaceRoots) {
18
- const rootPath = path.join(repoRoot, workspaceRoot)
19
- const entries = await readdir(rootPath, { withFileTypes: true })
20
-
21
- for (const entry of entries) {
22
- if (!entry.isDirectory()) {
23
- continue
24
- }
25
-
26
- packagePaths.push(path.join(rootPath, entry.name, 'package.json'))
27
- }
28
- }
13
+ return entries
14
+ .filter((entry) => entry.isDirectory())
15
+ .map((entry) => entry.name)
16
+ .sort()
17
+ }
29
18
 
30
- return packagePaths
19
+ function isValidCommandName(command) {
20
+ return typeof command === 'string' && /^[a-z0-9-]+$/i.test(command)
31
21
  }
32
22
 
33
23
  async function main() {
34
24
  const [command] = process.argv.slice(2)
25
+ const commands = await getAvailableCommands()
35
26
 
36
- switch (command) {
37
- case 'latest':
38
- await runLatest()
39
- return
40
- default:
41
- printHelp()
42
- process.exitCode = 1
27
+ if (!command || !(await runCommand(command))) {
28
+ printHelp(commands)
29
+ process.exitCode = 1
43
30
  }
44
31
  }
45
32
 
46
- function printHelp() {
33
+ function printHelp(commands) {
47
34
  console.log(`brick cli
48
35
 
49
36
  Usage:
50
- brick latest`)
51
- }
37
+ brick <command>
52
38
 
53
- function renderProgress(percent) {
54
- const completed = Math.round(percent / 10)
55
- return `${'#'.repeat(completed)}${'.'.repeat(10 - completed)}`
39
+ Commands:
40
+ ${commands.join('\n ')}`)
56
41
  }
57
42
 
58
- async function runLatest() {
59
- console.log('\x1b[32mPackage update')
60
-
61
- const packagePaths = await getWorkspacePackageJsonPaths()
62
- let index = 0
63
-
64
- for (const packagePath of packagePaths) {
65
- index += 1
66
-
67
- const packageJson = JSON.parse(await readFile(packagePath, 'utf8'))
68
- await updatePackageVersions(packageJson)
69
- await writeFile(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`)
70
-
71
- const percent = Math.round((index * 100) / packagePaths.length)
72
- process.stdout.write(`\rUpdating: [${renderProgress(percent)}] ${percent}%`)
43
+ async function runCommand(command) {
44
+ if (!isValidCommandName(command)) {
45
+ return false
73
46
  }
74
47
 
75
- process.stdout.write('\n')
76
- console.log('\x1b[0m')
77
- }
78
-
79
- async function updatePackageVersions(packageJson) {
80
- const dependencyEntries = [
81
- ...Object.entries(packageJson.dependencies || {}).map(([name, version]) => ({
82
- group: 'dependencies',
83
- name,
84
- version,
85
- })),
86
- ...Object.entries(packageJson.devDependencies || {}).map(([name, version]) => ({
87
- group: 'devDependencies',
88
- name,
89
- version,
90
- })),
91
- ]
48
+ const commandEntry = path.join(commandsDir, command, 'index.js')
92
49
 
93
- await Promise.all(
94
- dependencyEntries.map(async ({ group, name, version }) => {
95
- if (version === 'workspace:*' || version === 'workspace: *') {
96
- return
97
- }
50
+ try {
51
+ await access(commandEntry)
52
+ } catch {
53
+ return false
54
+ }
98
55
 
99
- try {
100
- const { stdout } = await execFileAsync('npm', ['view', name, 'version'])
101
- packageJson[group][name] = stdout.trim()
102
- } catch (error) {
103
- console.info(error instanceof Error ? error.message : String(error))
104
- }
105
- }),
106
- )
56
+ await import(pathToFileURL(commandEntry).href)
57
+ return true
107
58
  }
108
59
 
109
60
  await main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickflow/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
@@ -14,6 +14,18 @@
14
14
  "format": "pnpm exec prettier . --ignore-path ../../.prettierignore --write",
15
15
  "format:check": "pnpm exec prettier . --ignore-path ../../.prettierignore --check"
16
16
  },
17
+ "dependencies": {
18
+ "@babel/parser": "^7.29.7",
19
+ "@babel/traverse": "^7.29.7",
20
+ "@babel/types": "^7.29.7",
21
+ "@google/genai": "^2.6.0",
22
+ "@vue/compiler-sfc": "^3.5.35",
23
+ "fantasticon": "^4.1.0",
24
+ "glob": "^13.0.6",
25
+ "looks-same": "^10.0.1",
26
+ "sharp": "^0.34.5",
27
+ "svgo": "^4.0.1"
28
+ },
17
29
  "publishConfig": {
18
30
  "access": "public"
19
31
  },
@@ -0,0 +1,79 @@
1
+ import fs from 'fs'
2
+ import { globSync } from 'glob'
3
+ import path from 'path'
4
+
5
+ const args = process.argv.slice(3)
6
+ const extensions = '.{png,jpg,jpeg,webp}'
7
+
8
+ if (args.includes('--help') || args.includes('-h')) {
9
+ printHelp()
10
+ process.exit(0)
11
+ }
12
+
13
+ if (!args[0]) {
14
+ printHelp()
15
+ process.exit(1)
16
+ }
17
+
18
+ console.log('\x1b[32m', '\rIMG Optimize')
19
+
20
+ const patternPrefix = resolvePatternPrefix(args[0])
21
+ const searchPattern = `${patternPrefix}${extensions}`
22
+ const imgList = globSync(searchPattern)
23
+
24
+ if (imgList.length === 0) {
25
+ console.log(`\nNothing found for pattern: ${searchPattern}`)
26
+ console.log('\x1b[0m', '')
27
+ process.exit(0)
28
+ }
29
+
30
+ const { default: sharp } = await import('sharp')
31
+
32
+ for (let i = 0; i < imgList.length; i++) {
33
+ const imgPath = imgList[i]
34
+ await sharp(imgPath)
35
+ .avif({
36
+ effort: 7,
37
+ quality: 70,
38
+ })
39
+ .toFile(`${imgPath.replace(/\.(png|jpg|jpeg|webp)$/i, '')}.avif`)
40
+
41
+ const percent = Math.round(((i + 1) * 100) / imgList.length)
42
+ fs.unlinkSync(imgPath)
43
+ process.stdout.write(`\r💎 Optimizing: [${'🌹'.repeat((percent * 10) / 100)}] ${percent}%`)
44
+ }
45
+
46
+ console.log('\x1b[0m', '')
47
+
48
+ function printHelp() {
49
+ console.log(`brick avif <pattern>
50
+
51
+ Usage:
52
+ brick avif ../brick/public/**/*
53
+ brick avif ./src/assets/**/*
54
+ brick avif ./src/assets
55
+
56
+ Notes:
57
+ The command always searches only for ${extensions}
58
+ Pass a directory or a glob prefix without the extension part`)
59
+ }
60
+
61
+ function resolvePatternPrefix(input) {
62
+ if (/[?*[\]{}]/.test(input)) {
63
+ return stripTrailingExtensionGroup(input)
64
+ }
65
+
66
+ if (/[\\/]$/.test(input)) {
67
+ return `${input}**/*`
68
+ }
69
+
70
+ if (fs.existsSync(input) && fs.statSync(input).isDirectory()) {
71
+ return path.join(input, '**/*').replace(/\\/g, '/')
72
+ }
73
+
74
+ return stripTrailingExtensionGroup(input)
75
+ }
76
+
77
+ function stripTrailingExtensionGroup(input) {
78
+ return input.replace(/\.\{png,jpg,jpeg,webp\}$/i, '')
79
+ }
@@ -0,0 +1,70 @@
1
+ import fs from 'fs'
2
+ import { globSync } from 'glob'
3
+
4
+ const args = process.argv.slice(3)
5
+
6
+ if (args.includes('--help') || args.includes('-h')) {
7
+ printHelp()
8
+ process.exit(0)
9
+ }
10
+
11
+ if (args.length > 0) {
12
+ printHelp()
13
+ process.exit(1)
14
+ }
15
+
16
+ console.log('\x1b[32m', '\rFilename analyse')
17
+ const dsStore = globSync('./**/.DS_Store')
18
+ if (dsStore.length > 0) {
19
+ console.log('\x1b[0m', '')
20
+ dsStore.forEach((item) => {
21
+ fs.unlinkSync(item)
22
+ console.log(item)
23
+ })
24
+
25
+ throw `🥌 Remove .DS_Store ${dsStore[0]}`
26
+ }
27
+
28
+ const allFile = globSync('./**/*.*')
29
+
30
+ let errorFilename = ''
31
+ allFile.forEach((path) => {
32
+ if (/\.md/.test(path)) {
33
+ return
34
+ }
35
+ if (/node_modules/.test(path)) {
36
+ return
37
+ }
38
+ const filename = path.match(/([^/\\]+).[a-z\d]$/)?.[0]
39
+ if (!filename) {
40
+ console.warn(path)
41
+ }
42
+ if (/[A-Z]{3}.svg/.test(filename)) {
43
+ return
44
+ }
45
+ const isKebabCase = !/[^a-z./\d_-]/.test(filename)
46
+ if (!isKebabCase) {
47
+ errorFilename = `${errorFilename}
48
+ 🍖 No Kebab 🗺️ ${path} | ${filename}
49
+ `
50
+ }
51
+ })
52
+
53
+ if (errorFilename.length > 0) {
54
+ console.log('\x1b[0m', '')
55
+ throw errorFilename
56
+ }
57
+
58
+ console.log('\x1b[0m', '')
59
+
60
+ function printHelp() {
61
+ console.log(`brick filename
62
+
63
+ Usage:
64
+ brick filename
65
+
66
+ Notes:
67
+ Scans files under the current working directory
68
+ Removes .DS_Store files and fails on non-kebab-case filenames
69
+ Ignores markdown files and node_modules`)
70
+ }
@@ -0,0 +1,78 @@
1
+ import fs from 'fs'
2
+ import { globSync } from 'glob'
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const args = process.argv.slice(3)
7
+
8
+ if (args.includes('--help') || args.includes('-h')) {
9
+ printHelp()
10
+ process.exit(0)
11
+ }
12
+
13
+ if (args.length > 0) {
14
+ printHelp()
15
+ process.exit(1)
16
+ }
17
+
18
+ const currentDir = path.dirname(fileURLToPath(import.meta.url))
19
+ const workspaceRoot = path.resolve(currentDir, '../../../..')
20
+ const packageJsonPaths = globSync('**/package.json', {
21
+ absolute: true,
22
+ cwd: workspaceRoot,
23
+ ignore: ['**/node_modules/**'],
24
+ }).sort()
25
+
26
+ const dependenciesMap = new Map()
27
+
28
+ for (const packageJsonPath of packageJsonPaths) {
29
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
30
+ const packageLabel = getPackageLabel(packageJsonPath)
31
+
32
+ addDependencies(packageLabel, packageJson.dependencies)
33
+ addDependencies(packageLabel, packageJson.devDependencies)
34
+ }
35
+
36
+ const table = {}
37
+
38
+ for (const [dependencyName, versionsByPackage] of dependenciesMap) {
39
+ const uniqueVersions = new Set(Object.values(versionsByPackage))
40
+
41
+ if (uniqueVersions.size > 1) {
42
+ table[dependencyName] = versionsByPackage
43
+ }
44
+ }
45
+
46
+ console.table(table)
47
+
48
+ function addDependencies(packageLabel, dependencyGroup) {
49
+ if (!dependencyGroup) {
50
+ return
51
+ }
52
+
53
+ for (const [dependencyName, version] of Object.entries(dependencyGroup)) {
54
+ const currentVersions = dependenciesMap.get(dependencyName) ?? {}
55
+ dependenciesMap.set(dependencyName, {
56
+ ...currentVersions,
57
+ [packageLabel]: version,
58
+ })
59
+ }
60
+ }
61
+
62
+ function getPackageLabel(packageJsonPath) {
63
+ const relativePath = path.relative(workspaceRoot, packageJsonPath).replace(/\\/g, '/')
64
+ const packageDir = path.dirname(relativePath)
65
+
66
+ return packageDir === '.' ? 'root' : packageDir
67
+ }
68
+
69
+ function printHelp() {
70
+ console.log(`brick graph
71
+
72
+ Usage:
73
+ brick graph
74
+
75
+ Notes:
76
+ Scans all package.json files in the repository, including the root one
77
+ Prints only dependencies that have different versions across packages`)
78
+ }
@@ -0,0 +1,162 @@
1
+ import { FontAssetType, generateFonts, OtherAssetType } from 'fantasticon'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const currentDir = path.dirname(fileURLToPath(import.meta.url))
7
+ const workspaceRoot = path.resolve(currentDir, '../../../..')
8
+ const args = process.argv.slice(3)
9
+
10
+ if (args.includes('--help') || args.includes('-h')) {
11
+ printHelp()
12
+ process.exit(0)
13
+ }
14
+
15
+ const options = parseArgs(args)
16
+
17
+ if (!options.path) {
18
+ printHelp()
19
+ process.exit(1)
20
+ }
21
+
22
+ const inputDir = path.resolve(process.cwd(), options.path)
23
+ const iconName = options.name || path.basename(inputDir)
24
+ const outputDir = options.output
25
+ ? path.resolve(process.cwd(), options.output)
26
+ : path.resolve(workspaceRoot, `brick/assets/icons/${iconName}`)
27
+
28
+ if (!fs.existsSync(inputDir)) {
29
+ throw new Error(`Icon input directory is not found: ${inputDir}`)
30
+ }
31
+
32
+ if (!fs.statSync(inputDir).isDirectory()) {
33
+ throw new Error(`Icon input path must be a directory: ${inputDir}`)
34
+ }
35
+
36
+ if (/[\\/]/.test(iconName)) {
37
+ throw new Error(`Icon name must not contain path separators: ${iconName}`)
38
+ }
39
+
40
+ fs.mkdirSync(outputDir, { recursive: true })
41
+
42
+ await generateFonts({
43
+ assetTypes: [OtherAssetType.CSS, OtherAssetType.JSON],
44
+ fontsUrl: '.',
45
+ fontTypes: [FontAssetType.EOT, FontAssetType.WOFF2, FontAssetType.WOFF],
46
+ formatOptions: {
47
+ json: {
48
+ indent: 2,
49
+ },
50
+ },
51
+ inputDir,
52
+ name: iconName,
53
+ normalize: true,
54
+ outputDir,
55
+ })
56
+
57
+ const cssSourceFile = [`${iconName}.css`, 'icons.css', 'icon.css'].find((file) =>
58
+ fs.existsSync(path.join(outputDir, file)),
59
+ )
60
+
61
+ if (!cssSourceFile) {
62
+ throw new Error(`CSS output file was not generated for icon set "${iconName}"`)
63
+ }
64
+
65
+ const cssPath = path.join(outputDir, cssSourceFile)
66
+ const minifiedCssPath = path.join(outputDir, `${path.parse(cssSourceFile).name}.minify.css`)
67
+ const cssContent = fs.readFileSync(cssPath, 'utf8')
68
+ const normalizedCssContent = normalizeGeneratedCss(cssContent)
69
+
70
+ if (normalizedCssContent !== cssContent) {
71
+ fs.writeFileSync(cssPath, normalizedCssContent)
72
+ }
73
+
74
+ fs.writeFileSync(minifiedCssPath, minifyCss(normalizedCssContent))
75
+
76
+ console.log(`✅ Icons generated for ${iconName}`)
77
+ console.log(` input: ${inputDir}`)
78
+ console.log(` output: ${outputDir}`)
79
+
80
+ function minifyCss(css) {
81
+ return css
82
+ .replace(/\/\*[\s\S]*?\*\//g, '')
83
+ .replace(/\s+/g, ' ')
84
+ .replace(/\s*([{}:;,>+~])\s*/g, '$1')
85
+ .replace(/;\}/g, '}')
86
+ .trim()
87
+ }
88
+
89
+ function normalizeGeneratedCss(css) {
90
+ return `${css
91
+ .replace(/"/g, "'")
92
+ .replace(/^ {4}/gm, ' ')
93
+ .replace(/src: ([^\n]+),\n([^\n]+),\n([^\n]+);/, 'src:\n $1,\n $2,\n $3;')
94
+ .replace(
95
+ /i\[class\^='icon-'\]:before, i\[class\*=' icon-'\]:before \{/,
96
+ "i[class^='icon-']:before,\ni[class*=' icon-']:before {",
97
+ )
98
+ .replace(/\n{3,}/g, '\n\n')
99
+ .trim()}\n`
100
+ }
101
+
102
+ function parseArgs(rawArgs) {
103
+ const parsedOptions = {
104
+ name: null,
105
+ output: null,
106
+ path: null,
107
+ }
108
+ const positional = []
109
+
110
+ for (let index = 0; index < rawArgs.length; index += 1) {
111
+ const value = rawArgs[index]
112
+
113
+ if (value === '--path') {
114
+ parsedOptions.path = rawArgs[index + 1] ?? null
115
+ index += 1
116
+ continue
117
+ }
118
+
119
+ if (value === '--name') {
120
+ parsedOptions.name = rawArgs[index + 1] ?? null
121
+ index += 1
122
+ continue
123
+ }
124
+
125
+ if (value === '--output') {
126
+ parsedOptions.output = rawArgs[index + 1] ?? null
127
+ index += 1
128
+ continue
129
+ }
130
+
131
+ positional.push(value)
132
+ }
133
+
134
+ if (!parsedOptions.path) {
135
+ parsedOptions.path = positional[0] ?? null
136
+ }
137
+
138
+ if (!parsedOptions.name) {
139
+ parsedOptions.name = positional[1] ?? null
140
+ }
141
+
142
+ if (!parsedOptions.output) {
143
+ parsedOptions.output = positional[2] ?? null
144
+ }
145
+
146
+ return parsedOptions
147
+ }
148
+
149
+ function printHelp() {
150
+ console.log(`brick icon <path> [name] [output]
151
+
152
+ Usage:
153
+ brick icon ./path/to/svg-icons
154
+ brick icon ./path/to/svg-icons marketing
155
+ brick icon ./path/to/svg-icons marketing ./path/to/output
156
+ brick icon --path ./path/to/svg-icons --name marketing --output ./path/to/output
157
+
158
+ Notes:
159
+ path: directory with source .svg icons
160
+ name: icon set name and generated asset prefix
161
+ output: directory for generated files; default is brick/assets/icons/<name>`)
162
+ }