feat(cli): ✨ project structure detection
This commit is contained in:
5
packages/cli/jsconfig.json
Normal file
5
packages/cli/jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"exclude": ["./node_modules/**", "./dist/**"],
|
||||
"include": ["./index.d.ts", "./src/**/*.ts", "./src/**/*.js"],
|
||||
}
|
||||
41
packages/cli/package.json
Normal file
41
packages/cli/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@eslit/cli",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"email": "contact.guz013@gmail.com",
|
||||
"name": "Gustavo \"Guz\" L. de Mello",
|
||||
"url": "https://guz.one"
|
||||
},
|
||||
"module": "./src/index.js",
|
||||
"source": "./src/index.js",
|
||||
"files": [
|
||||
"src",
|
||||
"index.d.ts"
|
||||
],
|
||||
"homepage": "https://github.com/LoredDev/ESLit",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"directory": "packages/config",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LoredDev/ESLit"
|
||||
},
|
||||
"bin": "./src/index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"magic-string": "^0.30.2",
|
||||
"nanospinner": "^1.1.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"picomatch": "^2.3.1",
|
||||
"prompts": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.4.2",
|
||||
"yaml": "^2.3.1"
|
||||
}
|
||||
}
|
||||
225
packages/cli/src/index.js
Executable file
225
packages/cli/src/index.js
Executable file
@@ -0,0 +1,225 @@
|
||||
#!node
|
||||
import fs from 'node:fs/promises';
|
||||
import path, { join } from 'node:path';
|
||||
import { existsSync } from 'node:fs';
|
||||
import YAML from 'yaml';
|
||||
import glob from 'picomatch';
|
||||
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @param {Promise<T>} promise - The async function to try running
|
||||
* @returns {Promise<T | null>} - Returns the result of the async function, or null if it errors
|
||||
*/
|
||||
async function tryRun(promise) {
|
||||
try {
|
||||
return await promise;
|
||||
}
|
||||
catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directory - The directory to find .gitignore and .eslintignore
|
||||
* @returns {Promise<string[]>} - List of ignore glob patterns
|
||||
*/
|
||||
async function getIgnoredFiles(directory) {
|
||||
const gitIgnore = (await tryRun(fs.readFile(join(directory, '.gitignore'), 'utf8')) ?? '')
|
||||
.split('\n')
|
||||
.filter(p => p && !p.startsWith('#'))
|
||||
.map(p => join(directory, '**', p));
|
||||
|
||||
const eslintIgnore = (await tryRun(fs.readFile(join(directory, '.eslintignore'), 'utf8')) ?? '')
|
||||
.split('\n')
|
||||
.filter(p => p && !p.startsWith('#'))
|
||||
.map(p => join(directory, '**', p));
|
||||
|
||||
return [...eslintIgnore, ...gitIgnore];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directory - The directory to work in.
|
||||
* @returns {Promise<string>} - The package name founded.
|
||||
*/
|
||||
async function getPackageName(directory) {
|
||||
if (existsSync(join(directory, 'package.json'))) {
|
||||
const file = await fs.readFile(join(directory, 'package.json'), 'utf8');
|
||||
/** @type {{name?: string}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const obj = JSON.parse(file);
|
||||
|
||||
if (obj.name) return obj.name;
|
||||
}
|
||||
return path.normalize(directory).split('/').at(-1) ?? directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directory - The directory path to work on
|
||||
* @param {string[]} files - The file list to be filtered
|
||||
* @param {string[]} [packages] - The packages to be filtered
|
||||
* @returns {Promise<import('./types').Package>} - The package object
|
||||
*/
|
||||
async function getRootPackage(directory, files, packages = []) {
|
||||
|
||||
const ignorePatterns = [
|
||||
...packages.map(p =>
|
||||
`${join(directory, p, '**/*')}`,
|
||||
)];
|
||||
|
||||
console.log(ignorePatterns);
|
||||
|
||||
return {
|
||||
name: `${await getPackageName(directory)} [ROOT]`,
|
||||
files: files.filter(f =>
|
||||
// glob.isMatch(f, join(directory, '*/**')) &&
|
||||
!glob.isMatch(f, ignorePatterns),
|
||||
) ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
class Cli {
|
||||
/** @type {string} */
|
||||
dir = process.cwd();
|
||||
|
||||
/**
|
||||
* @param {string} [directory] - The directory to the cli work on
|
||||
* @param {string[]} [packages] - List of packages paths in the workspace
|
||||
*/
|
||||
constructor(
|
||||
directory,
|
||||
packages,
|
||||
) {
|
||||
this.dir ||= path.normalize(directory ?? this.dir);
|
||||
this.packages ||= packages;
|
||||
}
|
||||
|
||||
/** @type {{files: string[], directories: string[]} | undefined} */
|
||||
#paths;
|
||||
|
||||
/**
|
||||
* @param {string} [directory] - The directory to work on
|
||||
* @param {string[]} [ignores] - Glob patterns to ignore
|
||||
* @returns {Promise<{files: string[], directories: string[]}>} - List of all files in the directory
|
||||
*/
|
||||
async getPaths(directory = this.dir, ignores = []) {
|
||||
|
||||
ignores.push(
|
||||
...[
|
||||
'.git',
|
||||
'.dist',
|
||||
'.DS_Store',
|
||||
'node_modules',
|
||||
].map((f) => join(directory, f)),
|
||||
...await getIgnoredFiles(directory),
|
||||
);
|
||||
|
||||
const paths = (await fs.readdir(directory))
|
||||
.map((f) => path.normalize(join(directory, f)))
|
||||
.filter((p) => !glob.isMatch(p, ignores));
|
||||
|
||||
/** @type {string[]} */
|
||||
const files = [];
|
||||
/** @type {string[]} */
|
||||
const directories = [];
|
||||
|
||||
for (const path of paths) {
|
||||
if ((await fs.lstat(path)).isDirectory()) {
|
||||
const subPaths = await this.getPaths(path, ignores);
|
||||
directories.push(path, ...subPaths.directories);
|
||||
files.push(...subPaths.files);
|
||||
}
|
||||
else {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
return { files, directories };
|
||||
}
|
||||
|
||||
/** @type {string[] | undefined} */
|
||||
packages;
|
||||
|
||||
/**
|
||||
* @returns {Promise<string[]>} - List of packages on a directory;
|
||||
*/
|
||||
async getPackages() {
|
||||
|
||||
/** @type {string[]} */
|
||||
let packages = [];
|
||||
|
||||
const pnpmWorkspace =
|
||||
existsSync(join(this.dir, 'pnpm-workspace.yaml'))
|
||||
? 'pnpm-workspace.yaml'
|
||||
: existsSync(join(this.dir, 'pnpm-workspace.yml'))
|
||||
? 'pnpm-workspace.yml'
|
||||
: null;
|
||||
|
||||
if (pnpmWorkspace) {
|
||||
const fileYaml = await fs.readFile(join(this.dir, pnpmWorkspace), 'utf8');
|
||||
|
||||
/** @type {{packages?: string[]}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const fileObj = YAML.parse(fileYaml);
|
||||
|
||||
packages.push(...(fileObj?.packages ?? []));
|
||||
}
|
||||
else if (existsSync(join(this.dir, 'package.json'))) {
|
||||
const packageJson = await fs.readFile(join(this.dir, 'package.json'), 'utf8');
|
||||
|
||||
/** @type {{workspaces?: string[]}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const packageObj = JSON.parse(packageJson);
|
||||
|
||||
packages.push(...(packageObj?.workspaces ?? []));
|
||||
}
|
||||
return packages;
|
||||
}
|
||||
|
||||
/** @type {import('./types').Workspace | undefined} */
|
||||
#workspace;
|
||||
|
||||
/**
|
||||
* @returns {Promise<import('./types').Workspace>}
|
||||
* The workspace structure and packages founded
|
||||
*/
|
||||
async getWorkspace() {
|
||||
console.log(this.packages);
|
||||
const rootPackage = await getRootPackage(this.dir, this.#paths?.files ?? [], this.packages);
|
||||
|
||||
/** @type {string[]} */
|
||||
const packagesPaths = this.#paths?.directories.filter(d =>
|
||||
glob.isMatch(d, this.packages?.map(p => join(this.dir, p)) ?? ''),
|
||||
) ?? [];
|
||||
|
||||
/** @type {import('./types').Package[]} */
|
||||
const packages = [];
|
||||
|
||||
for (const pkgPath of packagesPaths) {
|
||||
packages.push({
|
||||
name: await getPackageName(pkgPath),
|
||||
files: this.#paths?.files.filter(f => glob.isMatch(f, join(pkgPath, '**/*'))) ?? [],
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
packages: [
|
||||
rootPackage,
|
||||
...packages,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.packages ||= await this.getPackages();
|
||||
this.#paths = await this.getPaths();
|
||||
this.#workspace = await this.getWorkspace();
|
||||
|
||||
|
||||
|
||||
console.log(this.dir);
|
||||
console.log(this.#workspace.packages);
|
||||
}
|
||||
}
|
||||
|
||||
await new Cli().run();
|
||||
17
packages/cli/src/types.d.ts
vendored
Normal file
17
packages/cli/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
interface Options {
|
||||
environment: {
|
||||
node: boolean
|
||||
deno: boolean
|
||||
browser: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface Workspace {
|
||||
packages: Package[]
|
||||
}
|
||||
|
||||
export interface Package {
|
||||
name: string
|
||||
files: string[]
|
||||
}
|
||||
74
pnpm-lock.yaml
generated
74
pnpm-lock.yaml
generated
@@ -21,6 +21,9 @@ importers:
|
||||
'@commitlint/types':
|
||||
specifier: ^17.4.4
|
||||
version: 17.4.4
|
||||
'@eslit/cli':
|
||||
specifier: workspace:*
|
||||
version: link:packages/cli
|
||||
'@svitejs/changesets-changelog-github-compact':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
@@ -34,8 +37,17 @@ importers:
|
||||
specifier: ^1.10.9
|
||||
version: 1.10.9
|
||||
|
||||
fixtures/library:
|
||||
dependencies:
|
||||
'@eslit/cli':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/cli
|
||||
|
||||
fixtures/svelte:
|
||||
devDependencies:
|
||||
'@eslit/cli':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/cli
|
||||
'@fontsource/fira-mono':
|
||||
specifier: ^4.5.10
|
||||
version: 4.5.10
|
||||
@@ -79,6 +91,31 @@ importers:
|
||||
specifier: ^4.4.2
|
||||
version: 4.4.2
|
||||
|
||||
packages/cli:
|
||||
dependencies:
|
||||
magic-string:
|
||||
specifier: ^0.30.2
|
||||
version: 0.30.2
|
||||
nanospinner:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
picocolors:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
picomatch:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.4.2
|
||||
version: 20.4.2
|
||||
yaml:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
|
||||
packages/config:
|
||||
dependencies:
|
||||
'@eslint/eslintrc':
|
||||
@@ -99,9 +136,6 @@ importers:
|
||||
globals:
|
||||
specifier: ^13.20.0
|
||||
version: 13.20.0
|
||||
yaml:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
devDependencies:
|
||||
'@types/eslint__js':
|
||||
specifier: ^8.42.0
|
||||
@@ -666,7 +700,6 @@ packages:
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.15:
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping@0.3.18:
|
||||
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
||||
@@ -2457,6 +2490,11 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/kleur@3.0.3:
|
||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/kleur@4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2547,6 +2585,13 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/magic-string@0.30.2:
|
||||
resolution: {integrity: sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: false
|
||||
|
||||
/map-obj@1.0.1:
|
||||
resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2649,6 +2694,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/nanospinner@1.1.0:
|
||||
resolution: {integrity: sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==}
|
||||
dependencies:
|
||||
picocolors: 1.0.0
|
||||
dev: false
|
||||
|
||||
/natural-compare-lite@1.4.0:
|
||||
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
|
||||
|
||||
@@ -2814,7 +2865,6 @@ packages:
|
||||
|
||||
/picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
|
||||
/picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
@@ -2887,6 +2937,14 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
kleur: 3.0.3
|
||||
sisteransi: 1.0.5
|
||||
dev: false
|
||||
|
||||
/pseudomap@1.0.2:
|
||||
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
|
||||
dev: true
|
||||
@@ -3118,6 +3176,10 @@ packages:
|
||||
totalist: 3.0.1
|
||||
dev: true
|
||||
|
||||
/sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
dev: false
|
||||
|
||||
/slash@3.0.0:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3753,7 +3815,7 @@ packages:
|
||||
/yaml@2.3.1:
|
||||
resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: false
|
||||
dev: true
|
||||
|
||||
/yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
|
||||
Reference in New Issue
Block a user