diff --git a/README.md b/README.md index 3fce890..bd480e2 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Commands: clean [files...] Cross env clean workspace dependencies Display Turbo monorepo dependencies graph dependents Display Turbo monorepo dependents graph + ls Display all Turbo repo packages updates Check if the dependencies of the workspace are up to date help [command] display help for command ``` @@ -46,6 +47,10 @@ Display a graph of dependencies within the monorepo. Display a graph of dependents within the monorepo. +### `ls` + +List all the packages in the monorepo + ### `updates` Check if the dependencies of the workspace are up to date diff --git a/package.json b/package.json index 15f5a34..54ae42c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "turbo-kit", - "version": "1.0.0", + "version": "1.1.0", "description": "Turborepo CLI for housekeeping", "license": "MIT", "repository": { @@ -27,14 +27,14 @@ "@turbo/repository": "^0.0.1-canary.10", "chalk": "^5.3.0", "commander": "^12.1.0", - "npm-check-updates": "^17.0.0-6" + "npm-check-updates": "^17.0.0" }, "devDependencies": { "@biomejs/biome": "^1.8.3", - "@types/node": "^20.14.12", - "husky": "^9.1.3", + "@types/node": "^20.14.13", + "husky": "^9.1.4", "tsup": "^8.2.3", "typescript": "^5.5.4" }, - "keywords": ["turbo"] + "keywords": ["turbo", "turborepo", "monorepo"] } diff --git a/src/bin/cli.ts b/src/bin/cli.ts index c6b9f71..d9b594c 100644 --- a/src/bin/cli.ts +++ b/src/bin/cli.ts @@ -4,6 +4,7 @@ import { Command } from 'commander'; import { clean } from '../commands/clean'; import { updates } from '../commands/updates'; import { dependenciesGraph, dependentsGraph } from '../commands/graph'; +import { list } from '../commands/ls'; const program = new Command(); @@ -15,11 +16,6 @@ program .argument('[files...]', 'Files or directories to be deleted') .action((files) => clean({ files })); -program - .command('updates') - .description('Check if the dependencies of the workspace are up to date') - .action(() => updates()); - program .command('dependencies') .description('Display Turbo monorepo dependencies graph') @@ -30,4 +26,14 @@ program .description('Display Turbo monorepo dependents graph') .action(() => dependentsGraph()); +program + .command('ls') + .description('Display all Turbo repo packages') + .action(() => list()); + +program + .command('updates') + .description('Check if the dependencies of the workspace are up to date') + .action(() => updates()); + program.parse(); diff --git a/src/commands/graph.ts b/src/commands/graph.ts index cb1388b..61cb1fc 100644 --- a/src/commands/graph.ts +++ b/src/commands/graph.ts @@ -4,11 +4,15 @@ import chalk from 'chalk'; const graph = async (key: keyof PackageDetails) => { const workspace = await Workspace.find('.'); - const packages = await workspace.findPackages(); + const packages = (await workspace.findPackages()).sort((a, b) => + a.relativePath.localeCompare(b.relativePath), + ); const packagesWithGraph = await workspace.findPackagesWithGraph(); for (const pkg of packages) { - const rawDependencies = packagesWithGraph[pkg.relativePath][key]; + const rawDependencies = packagesWithGraph[pkg.relativePath][key].sort( + (a, b) => a.localeCompare(b), + ); const dependencies = rawDependencies.map( (rawDep) => packages.find((pkg) => pkg.relativePath === rawDep)?.name, @@ -24,7 +28,7 @@ const graph = async (key: keyof PackageDetails) => { console.log(`${prefix} ${dep}${isLast ? '\n' : ''}`); }); } else { - console.log('└── None...\n'); + console.log('└── none...\n'); } } }; diff --git a/src/commands/ls.ts b/src/commands/ls.ts new file mode 100644 index 0000000..89f4b88 --- /dev/null +++ b/src/commands/ls.ts @@ -0,0 +1,18 @@ +import { Workspace } from '@turbo/repository'; +import { generateAsciiTree } from '@utils/ascii-tree'; +import chalk from 'chalk'; + +export const list = async () => { + const workspace = await Workspace.find('.'); + + const packages = (await workspace.findPackages()).sort((a, b) => + a.relativePath.localeCompare(b.relativePath), + ); + + const paths = packages.map( + (pkg) => + `${pkg.relativePath} | ${chalk.underline(chalk.bold(pkg.name))}`, + ); + const tree = generateAsciiTree(paths); + console.log(tree); +}; diff --git a/src/commands/updates.ts b/src/commands/updates.ts index c4e84fa..4280bcd 100644 --- a/src/commands/updates.ts +++ b/src/commands/updates.ts @@ -6,14 +6,16 @@ import chalk from 'chalk'; export const updates = async () => { const workspace = await Workspace.find('.'); - const packages = await workspace.findPackages(); + const packages = (await workspace.findPackages()).sort((a, b) => + a.relativePath.localeCompare(b.relativePath), + ); if (packages.length === 0) { console.warn('[Turbo Kit] - No packages found...'); process.exit(1); } - await packageUpdates('Root', 'package.json'); + await packageUpdates('root', 'package.json'); for (const pkg of packages) { await packageUpdates( @@ -39,6 +41,6 @@ const packageUpdates = async (name: string, path: string) => { console.log(`${prefix} ${key} >> ${value}${isLast ? '\n' : ''}`); }); } else { - console.log('└── None...\n'); + console.log('└── none...\n'); } }; diff --git a/src/utils/ascii-tree.ts b/src/utils/ascii-tree.ts new file mode 100644 index 0000000..f40ee8b --- /dev/null +++ b/src/utils/ascii-tree.ts @@ -0,0 +1,54 @@ +type TreeNode = { + [key: string]: TreeNode | null; +}; + +/** + * Copied from + * https://github.com/BuilderIO/micro-agent/blob/main/src/helpers/generate-ascii-tree.ts + */ +export const generateAsciiTree = (paths: string[]): string => { + const root: TreeNode = {}; + + // Construct the tree + for (const path of paths) { + const parsedPath = path.split(' | '); + const parts = parsedPath.shift()?.split(/\/|\\/g) || []; + + if (parsedPath.length > 0) { + const lastItem = parts.pop(); + parts.push(`${lastItem} | ${parsedPath}`); + } + + let current = root; + + parts.forEach((part, index) => { + if (!current[part]) { + current[part] = index === parts.length - 1 ? null : {}; + } + current = current[part] as TreeNode; + }); + } + + // Function to generate ASCII tree + const generateTreeString = (node: TreeNode, prefix = ''): string => { + const keys = Object.keys(node); + let result = ''; + + keys.forEach((key, index) => { + const isThisLast = index === keys.length - 1; + result += `${prefix + (isThisLast ? '└── ' : '├── ') + key}\n`; + + // Generate subtree if the current node is a directory + if (node[key]) { + result += generateTreeString( + node[key] as TreeNode, + prefix + (isThisLast ? ' ' : '│ '), + ); + } + }); + + return result; + }; + + return generateTreeString(root).trim(); +}; diff --git a/yarn.lock b/yarn.lock index 9d89534..1a060e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -385,10 +385,10 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/node@^20.14.12": - version "20.14.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49" - integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ== +"@types/node@^20.14.13": + version "20.14.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.13.tgz#bf4fe8959ae1c43bc284de78bd6c01730933736b" + integrity sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w== dependencies: undici-types "~5.26.4" @@ -677,10 +677,10 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -husky@^9.1.3: - version "9.1.3" - resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.3.tgz#46cddff01f9a551f87b39accc67860bce5d00680" - integrity sha512-ET3TQmQgdIu0pt+jKkpo5oGyg/4MQZpG6xcam5J5JyNJV+CBT23OBpCF15bKHKycRyMH9k6ONy8g2HdGIsSkMQ== +husky@^9.1.4: + version "9.1.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.4.tgz#926fd19c18d345add5eab0a42b2b6d9a80259b34" + integrity sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA== ignore@^5.2.0: version "5.3.1" @@ -819,10 +819,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-check-updates@^17.0.0-6: - version "17.0.0-6" - resolved "https://registry.yarnpkg.com/npm-check-updates/-/npm-check-updates-17.0.0-6.tgz#d47b879205dac74a0c9c7bb75bb304b8ead76275" - integrity sha512-XEUxNKXGaUuPo/4RZLRIcs2IHvVo9NSABZ2hPzjjefmgqmXgljTI6Xcgo0SZidFqiT3MScPVowtKpqMIN2PODA== +npm-check-updates@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/npm-check-updates/-/npm-check-updates-17.0.0.tgz#2d576117b978ac11bcae365859608b865716baed" + integrity sha512-rXJTiUYBa+GzlvPgemFlwlTdsqS2C16trlW58d9it8u3Hnp0M+Fzmd3NsYBFCjlRlgMZwzuCIBKd9bvIz6yx0Q== npm-run-path@^4.0.1: version "4.0.1"