repo-visualizer icon indicating copy to clipboard operation
repo-visualizer copied to clipboard

CLI Version

Open jlarmstrongiv opened this issue 3 years ago • 4 comments

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.

jlarmstrongiv avatar Mar 16 '22 06:03 jlarmstrongiv

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");
}

jlarmstrongiv avatar Mar 16 '22 06:03 jlarmstrongiv

Related https://github.com/githubocto/repo-visualizer/issues/41 and https://github.com/githubocto/repo-visualizer/issues/16

jlarmstrongiv avatar Mar 16 '22 06:03 jlarmstrongiv

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");
}

shixin-guo avatar Mar 18 '22 14:03 shixin-guo

great idea, we would definitely welcome a PR!

Wattenberger avatar Mar 25 '22 22:03 Wattenberger