CLI Version
It would be wonderful for the repo-visualizer to have a CLI version, so that it can used locally or in other environments.
Simply put, all the code is already written in https://github.com/githubocto/repo-visualizer/blob/main/src/index.jsx
Just make another version without the actions code, bundle, and publish. It is fairly straightforward with https://www.npmjs.com/package/tsup and https://www.npmjs.com/package/commander
For example:
import fs from "fs-extra";
import React from "react";
import ReactDOMServer from "react-dom/server.js";
import { Tree } from "../vendor/repo-visualizer/src/Tree";
import { processDir } from "../vendor/repo-visualizer/src/process-dir.js";
export const main = async () => {
const rootPath = ""; // Micro and minimatch do not support paths starting with ./
const maxDepth = 9;
const colorEncoding = "type";
const excludedPathsString =
"node_modules,bower_components,dist,out,build,eject,.next,.netlify,.yarn,.git,.vscode,package-lock.json,yarn.lock";
const excludedPaths = excludedPathsString.split(",").map((str) => str.trim());
// Split on semicolons instead of commas since ',' are allowed in globs, but ';' are not + are not permitted in file/folder names.
// const excludedGlobsString =
// ""
// "frontend/*.spec.js;**/*.{png,jpg};**/!(*.module).ts";
// const excludedGlobs = excludedGlobsString.split(";");
const excludedGlobs = [
"**/node_modules/**",
"**/dist/**",
"**/out/**",
"**/build/**",
"**/.cache/**",
"**/.next/**",
"**/.keystone/**",
"**/.vercel/**",
"**/package-lock.json",
"**/*.bundled_*.mjs",
"**/tmp/**",
"**/vendor/**",
"**/.DS_Store/**",
];
const data = await processDir(rootPath, excludedPaths, excludedGlobs);
const componentCodeString = ReactDOMServer.renderToStaticMarkup(
// @ts-ignore
<Tree data={data} maxDepth={+maxDepth} colorEncoding={colorEncoding} />,
);
const outputFile = "./repo-visualization.svg";
await fs.outputFile(outputFile, componentCodeString);
};
and
import { Command } from "commander";
import { readPackageUpSync } from "read-pkg-up";
import { main } from "../core";
const pkg = readPackageUpSync({ cwd: __dirname })!.packageJson;
export const program = new Command()
.name(pkg.name)
.description(pkg.description || pkg.name)
.version(pkg.version, "-v, --version")
.action((options: { version?: boolean }) => {
if (options.version) {
console.log(pkg.version);
} else {
main();
}
});
You may need to debug the react bundling following tips from https://github.com/egoist/tsup/issues/579 and https://github.com/egoist/tsup/issues/589 and replace lodash with lodash-es
I didn’t link together the options, but commanderjs can support them
Anyway, my rough proof of concept works, but I would much rather see the cli live here in the original, than in a fork.
For the curious, I am monkey-patching by running:
{
"run": "dist/cli.js",
"clean": "rm -rf src/vendor/repo-visualizer",
"postinstall": "npm run clean && git clone [email protected]:githubocto/repo-visualizer.git ./src/vendor/repo-visualizer && node ./src/vendor/repo-visualizer-postinstall",
}
and
import fg from "fast-glob";
import fs from "fs-extra";
import prependFile from "prepend-file";
import replace from "replace-in-file";
// commit b6ef9a212fc89efed1366f731fc4fe9a2cbc45ef
// always run from pkg.scripts or project root
const files = "src/vendor/repo-visualizer/src/**/*";
async function start() {
await Promise.all([replaceLodash(), replaceMicromatch(), noLogs(), noPkg()]);
await tsNoCheck();
}
start().catch(console.error);
async function replaceLodash() {
await replace({
files,
from: /import countBy from "lodash\/countBy";/g,
to: `import { countBy } from "lodash-es";`,
});
await replace({
files,
from: /import maxBy from "lodash\/maxBy";/g,
to: `import { maxBy } from "lodash-es";`,
});
await replace({
files,
from: /import entries from "lodash\/entries";/g,
to: `import { entries } from "lodash-es";`,
});
await replace({
files,
from: /import uniqBy from "lodash\/uniqBy";/g,
to: `import { uniqBy } from "lodash-es";`,
});
await replace({
files,
from: /import flatten from "lodash\/flatten";/g,
to: `import { flatten } from "lodash-es";`,
});
await replace({
files,
from: /import uniqueId from "lodash\/uniqueId";/g,
to: `import { uniqueId } from "lodash-es";`,
});
}
async function replaceMicromatch() {
await replace({
files,
from: /import { isMatch } from "micromatch";/g,
to: `import micromatch from "micromatch";`,
});
await replace({
files,
from: /isMatch\(/g,
to: `micromatch.isMatch(`,
});
}
async function tsNoCheck() {
const tsNoCheck = `// @ts-nocheck
`;
const filePaths = await fg(
"src/vendor/repo-visualizer/src/**/*.{js,jsx,ts,tsx}",
);
await Promise.all(
filePaths.map((filePath) => prependFile(filePath, tsNoCheck)),
);
}
async function noLogs() {
await replace({
files,
from: /console\.log\("Looking in ", `\.\/\${path}`\);/g,
to: ``,
});
}
async function noPkg() {
await fs.remove("src/vendor/repo-visualizer/package.json");
await fs.remove("src/vendor/repo-visualizer/yarn.lock");
}
Related https://github.com/githubocto/repo-visualizer/issues/41 and https://github.com/githubocto/repo-visualizer/issues/16
good idea, I like it
but,
why do we need to remove the package.json and lock file
async function noPkg() {
await fs.remove("src/vendor/repo-visualizer/package.json");
await fs.remove("src/vendor/repo-visualizer/yarn.lock");
}
For the curious, I am monkey-patching by running:
{ "run": "dist/cli.js", "clean": "rm -rf src/vendor/repo-visualizer", "postinstall": "npm run clean && git clone [email protected]:githubocto/repo-visualizer.git ./src/vendor/repo-visualizer && node ./src/vendor/repo-visualizer-postinstall", }and
import fg from "fast-glob"; import fs from "fs-extra"; import prependFile from "prepend-file"; import replace from "replace-in-file"; // commit b6ef9a212fc89efed1366f731fc4fe9a2cbc45ef // always run from pkg.scripts or project root const files = "src/vendor/repo-visualizer/src/**/*"; async function start() { await Promise.all([replaceLodash(), replaceMicromatch(), noLogs(), noPkg()]); await tsNoCheck(); } start().catch(console.error); async function replaceLodash() { await replace({ files, from: /import countBy from "lodash\/countBy";/g, to: `import { countBy } from "lodash-es";`, }); await replace({ files, from: /import maxBy from "lodash\/maxBy";/g, to: `import { maxBy } from "lodash-es";`, }); await replace({ files, from: /import entries from "lodash\/entries";/g, to: `import { entries } from "lodash-es";`, }); await replace({ files, from: /import uniqBy from "lodash\/uniqBy";/g, to: `import { uniqBy } from "lodash-es";`, }); await replace({ files, from: /import flatten from "lodash\/flatten";/g, to: `import { flatten } from "lodash-es";`, }); await replace({ files, from: /import uniqueId from "lodash\/uniqueId";/g, to: `import { uniqueId } from "lodash-es";`, }); } async function replaceMicromatch() { await replace({ files, from: /import { isMatch } from "micromatch";/g, to: `import micromatch from "micromatch";`, }); await replace({ files, from: /isMatch\(/g, to: `micromatch.isMatch(`, }); } async function tsNoCheck() { const tsNoCheck = `// @ts-nocheck `; const filePaths = await fg( "src/vendor/repo-visualizer/src/**/*.{js,jsx,ts,tsx}", ); await Promise.all( filePaths.map((filePath) => prependFile(filePath, tsNoCheck)), ); } async function noLogs() { await replace({ files, from: /console\.log\("Looking in ", `\.\/\${path}`\);/g, to: ``, }); } async function noPkg() { await fs.remove("src/vendor/repo-visualizer/package.json"); await fs.remove("src/vendor/repo-visualizer/yarn.lock"); }
great idea, we would definitely welcome a PR!