@spark-ui/cli-utils 2.12.9 → 2.13.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 +10 -0
- package/bin/spark-scan.mjs +15 -0
- package/bin/spark.mjs +1 -1
- package/package.json +4 -3
- package/src/generate/templates/component/[package.json].js +2 -2
- package/src/generate/templates/hook/[package.json].js +1 -1
- package/src/index.doc.mdx +70 -0
- package/src/scan/index.mjs +122 -0
- package/src/scan/scanCallback.mjs +62 -0
- package/src/scan/utils/extract-imports.mjs +28 -0
- package/src/scan/utils/file-contains-import.mjs +17 -0
- package/src/scan/utils/get-csv.mjs +0 -0
- package/src/scan/utils/get-formated-timestamp.mjs +7 -0
- package/src/scan/utils/index.mjs +5 -0
- package/src/scan/utils/logger.mjs +19 -0
- package/src/scan/utils/scan-directories.mjs +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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.13.0](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.12.10...@spark-ui/cli-utils@2.13.0) (2024-05-03)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **cli-utils:** add scan adoption script ([203e05e](https://github.com/adevinta/spark/commit/203e05e02285be18e5d0c6211f3ec04e4322837d))
|
|
11
|
+
|
|
12
|
+
## [2.12.10](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.12.9...@spark-ui/cli-utils@2.12.10) (2024-04-29)
|
|
13
|
+
|
|
14
|
+
**Note:** Version bump only for package @spark-ui/cli-utils
|
|
15
|
+
|
|
6
16
|
## [2.12.9](https://github.com/adevinta/spark/compare/@spark-ui/cli-utils@2.12.8...@spark-ui/cli-utils@2.12.9) (2024-04-08)
|
|
7
17
|
|
|
8
18
|
**Note:** Version bump only for package @spark-ui/cli-utils
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander'
|
|
4
|
+
import { adoption } from '../src/scan/index.mjs'
|
|
5
|
+
|
|
6
|
+
const program = new Command()
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.command('adoption')
|
|
10
|
+
.description('Scan @spark-ui adoption for .tsx files with given imports')
|
|
11
|
+
.option('-c, --configuration <path>', 'configuration file route', '.spark-ui.cjs')
|
|
12
|
+
.option('-o, --output <path>', 'output file route')
|
|
13
|
+
.action(adoption)
|
|
14
|
+
|
|
15
|
+
program.parse(process.argv)
|
package/bin/spark.mjs
CHANGED
|
@@ -9,6 +9,6 @@ const { version } = require('../package.json')
|
|
|
9
9
|
program.version(version, '--version')
|
|
10
10
|
|
|
11
11
|
program.command('generate', 'Generate a component scaffolding').alias('g')
|
|
12
|
-
program.command('
|
|
12
|
+
program.command('scan', 'Scan a directory for components').alias('s')
|
|
13
13
|
|
|
14
14
|
program.parse(process.argv)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spark-ui/cli-utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Spark CLI utils",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
],
|
|
13
13
|
"bin": {
|
|
14
14
|
"spark": "./bin/spark.mjs",
|
|
15
|
-
"spark-generate": "./bin/spark-generate.mjs"
|
|
15
|
+
"spark-generate": "./bin/spark-generate.mjs",
|
|
16
|
+
"spark-scan": "./bin/spark-scan.mjs"
|
|
16
17
|
},
|
|
17
18
|
"type": "module",
|
|
18
19
|
"repository": {
|
|
@@ -42,5 +43,5 @@
|
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@types/fs-extra": "11.0.4"
|
|
44
45
|
},
|
|
45
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "5d945ed8b0a31bf0d42382c560388c95a92f6411"
|
|
46
47
|
}
|
|
@@ -24,8 +24,8 @@ export default ({ name, description }) => `{
|
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@spark-ui/theme-utils": "^4.0.0",
|
|
27
|
-
"react": "^
|
|
28
|
-
"react-dom": "^
|
|
27
|
+
"react": "^18.0 || ^19.0",
|
|
28
|
+
"react-dom": "^18.0 || ^19.0",
|
|
29
29
|
"tailwindcss": "^3.0.0"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
package/src/index.doc.mdx
CHANGED
|
@@ -40,3 +40,73 @@ Then, a command prompt will guide you through the process by asking you for:
|
|
|
40
40
|
- the package name (required),
|
|
41
41
|
- the template used (required, only `Component` template available right now)
|
|
42
42
|
- and the package description (optional).
|
|
43
|
+
|
|
44
|
+
## Scanning directory adoption
|
|
45
|
+
|
|
46
|
+
For viewing the adoption of packages in a project directory, the following command can be executed:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
$ spark scan adoption
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Options
|
|
53
|
+
|
|
54
|
+
#### Configuration
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
$ spark scan adoption --configuration <filename>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
alias
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
$ spark scan adoption -c <filename>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
example
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
spark scan adoption -c "./spark-ui.cjs"
|
|
70
|
+
````
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
##### configuration filename structure
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
// .spark-ui.cjs
|
|
77
|
+
module.exports = {
|
|
78
|
+
adoption: {
|
|
79
|
+
details: true,
|
|
80
|
+
sort: 'count', // 'count' or 'alphabetical'
|
|
81
|
+
imports: ['@spark-ui'],
|
|
82
|
+
extensions: ['.tsx', '.ts'],
|
|
83
|
+
directory: './packages',
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/***
|
|
88
|
+
- `details` (boolean) - whether to show the details of the adoption or not. Default: false
|
|
89
|
+
- `sort` ('count' | 'alphabetical') - packages are sorted alphabetically. Default: false means sorted by adoption number
|
|
90
|
+
- `imports` (array) - the imports to be scanned.
|
|
91
|
+
- `extensions` (array) - the extensions to be scanned
|
|
92
|
+
- `directory` (string) - the directory to be scanned. Default: '.' means the current directory
|
|
93
|
+
***/
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Output
|
|
97
|
+
The output option is used to save the adoption data to a file. It is optional
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
$ spark scan adoption --output <filename>
|
|
101
|
+
```
|
|
102
|
+
alias
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
$ spark scan adoption -o <filename>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
example
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
spark scan adoption -o "./adoption.$(date +"%Y%m%d_%H:%M:%S").json"
|
|
112
|
+
````
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as process from 'node:process'
|
|
2
|
+
|
|
3
|
+
import { appendFileSync, existsSync } from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
|
|
6
|
+
import { scanCallback } from './scanCallback.mjs'
|
|
7
|
+
import { logger } from './utils/logger.mjs'
|
|
8
|
+
import { scanDirectories } from './utils/scan-directories.mjs'
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
adoption: {
|
|
12
|
+
details: false,
|
|
13
|
+
sort: 'count',
|
|
14
|
+
imports: ['@spark-ui'],
|
|
15
|
+
extensions: ['.tsx', '.ts'],
|
|
16
|
+
directory: '.',
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function adoption(options) {
|
|
21
|
+
let config = DEFAULT_CONFIG
|
|
22
|
+
|
|
23
|
+
const configFileRoute = path.join(process.cwd(), options.configuration || '.spark-ui.cjs')
|
|
24
|
+
try {
|
|
25
|
+
if (existsSync(configFileRoute)) {
|
|
26
|
+
console.log('✨✨✨ loading spark-ui custom configuration file ✨✨✨')
|
|
27
|
+
const { default: customConfig } = await import(
|
|
28
|
+
path.join(process.cwd(), options.configuration)
|
|
29
|
+
)
|
|
30
|
+
config = structuredClone(customConfig, DEFAULT_CONFIG)
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.info('ℹ️ Loading default configuration')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const extensions = config.adoption.extensions
|
|
37
|
+
|
|
38
|
+
let importCount = 0
|
|
39
|
+
const importResults = {}
|
|
40
|
+
let importsUsed = {}
|
|
41
|
+
let importsCount = {}
|
|
42
|
+
config.adoption.imports.forEach(moduleName => {
|
|
43
|
+
console.log(`scanning adoption for ${moduleName}`)
|
|
44
|
+
const directoryPath = path.join(process.cwd(), config.adoption.directory)
|
|
45
|
+
|
|
46
|
+
const response = scanDirectories(directoryPath, moduleName, extensions, scanCallback, {
|
|
47
|
+
importCount,
|
|
48
|
+
importResults,
|
|
49
|
+
importsUsed,
|
|
50
|
+
importsCount,
|
|
51
|
+
})
|
|
52
|
+
if (importCount !== response.importCount) {
|
|
53
|
+
logger.success(
|
|
54
|
+
`Found ${response.importCount - importCount} imports with "${moduleName}" modules across directory ${directoryPath}.`
|
|
55
|
+
)
|
|
56
|
+
} else {
|
|
57
|
+
logger.warn(`No files found with "${moduleName}" imports across directory ${directoryPath}.`)
|
|
58
|
+
}
|
|
59
|
+
importCount = response.importCount
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Sort importsUsed by alphabet
|
|
63
|
+
if (config.adoption.sort === 'alphabetical') {
|
|
64
|
+
importsUsed = Object.fromEntries(
|
|
65
|
+
Object.entries(importsUsed)
|
|
66
|
+
.sort(([pkgNameA], [pkgNameB]) => pkgNameA.localeCompare(pkgNameB))
|
|
67
|
+
.map(([pkgName, content]) => {
|
|
68
|
+
return [
|
|
69
|
+
pkgName,
|
|
70
|
+
{
|
|
71
|
+
default: Object.fromEntries(
|
|
72
|
+
Object.entries(content.default).sort(([a], [b]) => a.localeCompare(b))
|
|
73
|
+
),
|
|
74
|
+
named: Object.fromEntries(
|
|
75
|
+
Object.entries(content.named).sort(([a], [b]) => a.localeCompare(b))
|
|
76
|
+
),
|
|
77
|
+
importsCount: content.importsCount,
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
})
|
|
81
|
+
)
|
|
82
|
+
} else if (config.adoption.sort === 'count') {
|
|
83
|
+
// Sort importsUsed by most used
|
|
84
|
+
importsUsed = Object.fromEntries(
|
|
85
|
+
Object.entries(importsUsed)
|
|
86
|
+
.sort(([, contentA], [, contentB]) => contentB.importsCount - contentA.importsCount)
|
|
87
|
+
.map(([pkgName, content]) => {
|
|
88
|
+
return [
|
|
89
|
+
pkgName,
|
|
90
|
+
{
|
|
91
|
+
default: Object.fromEntries(
|
|
92
|
+
Object.entries(content.default).sort(([, a], [, b]) => b - a)
|
|
93
|
+
),
|
|
94
|
+
named: Object.fromEntries(
|
|
95
|
+
Object.entries(content.named).sort(([, a], [, b]) => b - a)
|
|
96
|
+
),
|
|
97
|
+
importsCount: content.importsCount,
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
importsCount = Object.fromEntries(Object.entries(importsCount).sort(([, a], [, b]) => b - a))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const result = Object.fromEntries(
|
|
107
|
+
Object.entries(importsUsed).map(([pkgName, value]) => [
|
|
108
|
+
pkgName,
|
|
109
|
+
{ ...value, ...(config.adoption.details && { results: importResults[pkgName] }) },
|
|
110
|
+
])
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if (options.output) {
|
|
114
|
+
try {
|
|
115
|
+
appendFileSync(`${options.output}`, JSON.stringify(result, null, 2))
|
|
116
|
+
} catch (err) {
|
|
117
|
+
logger.error(`Error writing file: ${err}`)
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
logger.info(JSON.stringify(result, null, 2))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import extractImports from './utils/extract-imports.mjs'
|
|
2
|
+
|
|
3
|
+
export function scanCallback(
|
|
4
|
+
f,
|
|
5
|
+
moduleName,
|
|
6
|
+
{ importCount, importResults, importsUsed, importsCount }
|
|
7
|
+
) {
|
|
8
|
+
const response = { importCount, importResults, importsUsed, importsCount }
|
|
9
|
+
if (!f.fileContent) return response
|
|
10
|
+
|
|
11
|
+
const imports = extractImports(f.filePath, moduleName)
|
|
12
|
+
|
|
13
|
+
Object.entries(imports).forEach(([key, importDeclarations]) => {
|
|
14
|
+
const moduleName = key.split('/').splice(0, 2).join('/')
|
|
15
|
+
importDeclarations.forEach(importDeclaration => {
|
|
16
|
+
const statement = importDeclaration.getText()
|
|
17
|
+
const defaultImport = importDeclaration.getDefaultImport()?.getText() || null
|
|
18
|
+
const namedImports = importDeclaration.getNamedImports().map(n => n.getText())
|
|
19
|
+
|
|
20
|
+
if (!importResults[moduleName]) {
|
|
21
|
+
importResults[moduleName] = []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
importResults[moduleName].push({
|
|
25
|
+
path: f.filePath,
|
|
26
|
+
statement,
|
|
27
|
+
hasDefault: !!defaultImport,
|
|
28
|
+
hasNamed: !!namedImports.length,
|
|
29
|
+
defaultImport,
|
|
30
|
+
namedImports,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
if (!importsUsed[moduleName]) {
|
|
34
|
+
importsUsed[moduleName] = {
|
|
35
|
+
default: {},
|
|
36
|
+
named: {},
|
|
37
|
+
importsCount: 0,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (defaultImport) {
|
|
42
|
+
importsUsed[moduleName].default[defaultImport] =
|
|
43
|
+
importsUsed[moduleName].default[defaultImport] + 1 || 1
|
|
44
|
+
importsUsed.importsCount = importsCount[defaultImport] + 1
|
|
45
|
+
|
|
46
|
+
importsCount[defaultImport] = importsCount[defaultImport] + 1 || 1
|
|
47
|
+
response.importCount++
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (namedImports.length) {
|
|
51
|
+
namedImports.forEach(n => {
|
|
52
|
+
importsUsed[moduleName].named[n] = importsUsed[moduleName].named[n] + 1 || 1
|
|
53
|
+
importsUsed[moduleName].importsCount = importsUsed[moduleName].importsCount + 1
|
|
54
|
+
importsCount[n] = importsCount[n] + 1 || 1
|
|
55
|
+
response.importCount++
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return response
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Project } from 'ts-morph'
|
|
2
|
+
|
|
3
|
+
export function extractImports(filePath, requestedModuleName) {
|
|
4
|
+
const project = new Project()
|
|
5
|
+
const sourceFile = project.addSourceFileAtPath(filePath)
|
|
6
|
+
|
|
7
|
+
const importStatements = {}
|
|
8
|
+
|
|
9
|
+
const importNodes = sourceFile.getImportDeclarations()
|
|
10
|
+
|
|
11
|
+
importNodes
|
|
12
|
+
.filter(node => {
|
|
13
|
+
const moduleName = node.getModuleSpecifierValue()
|
|
14
|
+
|
|
15
|
+
return moduleName.includes(requestedModuleName)
|
|
16
|
+
})
|
|
17
|
+
.forEach(node => {
|
|
18
|
+
const moduleName = node.getModuleSpecifierValue()
|
|
19
|
+
if (!importStatements[moduleName]) {
|
|
20
|
+
importStatements[moduleName] = []
|
|
21
|
+
}
|
|
22
|
+
importStatements[moduleName].push(node)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return importStatements
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default extractImports
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if a file contains an import from a given import name.
|
|
5
|
+
* @param filePath The path to the file to check.
|
|
6
|
+
* @param importName The name of the import to check for.
|
|
7
|
+
* @returns Whether the file contains an import from the given import name.
|
|
8
|
+
*/
|
|
9
|
+
export function fileContainsImport(filePath, importName) {
|
|
10
|
+
const fileContent = fs.readFileSync(filePath, 'utf8')
|
|
11
|
+
|
|
12
|
+
if (new RegExp(`import.*from\\s+["']${importName}.*["']`, 'm').test(fileContent)) {
|
|
13
|
+
return { filePath, fileContent }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return { filePath }
|
|
17
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { extractImports } from './extract-imports.mjs'
|
|
2
|
+
export { fileContainsImport } from './file-contains-import.mjs'
|
|
3
|
+
export { getFormatedTimestamp } from './get-formated-timestamp.mjs'
|
|
4
|
+
export { logger } from './logger.mjs'
|
|
5
|
+
export { scanDirectories } from './scan-directories.mjs'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
export const logger = {
|
|
4
|
+
error(...args) {
|
|
5
|
+
console.log(chalk.red(...args))
|
|
6
|
+
},
|
|
7
|
+
warn(...args) {
|
|
8
|
+
console.log(chalk.yellow(...args))
|
|
9
|
+
},
|
|
10
|
+
info(...args) {
|
|
11
|
+
console.log(chalk.cyan(...args))
|
|
12
|
+
},
|
|
13
|
+
success(...args) {
|
|
14
|
+
console.log(chalk.green(...args))
|
|
15
|
+
},
|
|
16
|
+
break() {
|
|
17
|
+
console.log('')
|
|
18
|
+
},
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import { fileContainsImport } from './file-contains-import.mjs'
|
|
5
|
+
|
|
6
|
+
export function scanDirectories(
|
|
7
|
+
directoryPath,
|
|
8
|
+
importName,
|
|
9
|
+
extensions,
|
|
10
|
+
scanningCallback,
|
|
11
|
+
{ importCount, importResults, importsUsed, importsCount }
|
|
12
|
+
) {
|
|
13
|
+
const files = fs.readdirSync(directoryPath)
|
|
14
|
+
|
|
15
|
+
let response = {
|
|
16
|
+
importCount,
|
|
17
|
+
importResults,
|
|
18
|
+
importsUsed,
|
|
19
|
+
importsCount,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const filePath = path.join(directoryPath, file)
|
|
24
|
+
const stats = fs.statSync(filePath)
|
|
25
|
+
|
|
26
|
+
if (stats.isDirectory()) {
|
|
27
|
+
response = scanDirectories(filePath, importName, extensions, scanningCallback, response)
|
|
28
|
+
} else if (stats.isFile() && extensions.includes(path.extname(filePath))) {
|
|
29
|
+
const f = fileContainsImport(filePath, importName)
|
|
30
|
+
|
|
31
|
+
if (f) {
|
|
32
|
+
response = scanningCallback(
|
|
33
|
+
{
|
|
34
|
+
filePath: f.filePath,
|
|
35
|
+
fileContent: f.fileContent,
|
|
36
|
+
},
|
|
37
|
+
importName,
|
|
38
|
+
response
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return response
|
|
45
|
+
}
|