├── .github
└── workflows
│ └── test.yaml
├── .gitignore
├── .husky
└── pre-commit
├── .npmignore
├── LICENSE
├── README.md
├── action.yml
├── diagram.svg
├── index.js
├── jest.config.cjs
├── package.json
├── src
├── CircleText.tsx
├── Tree.tsx
├── index.jsx
├── language-colors.json
├── process-dir.js
├── should-exclude-path.test.ts
├── should-exclude-path.ts
├── types.ts
└── utils.ts
├── tsconfig.json
└── yarn.lock
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: JS - Unit Tests
2 |
3 | on: push
4 |
5 | jobs:
6 |
7 | run-tests:
8 | name: Unit Tests
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Check out Git repository
13 | uses: actions/checkout@v2
14 |
15 | - name: Set up Node.js
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: 14
19 |
20 | - name: Install dependencies with caching
21 | uses: bahmutov/npm-install@v1
22 |
23 | - name: Check types
24 | run: |
25 | yarn run typecheck
26 |
27 | - name: Run unit tests
28 | run: |
29 | yarn run test
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run build
5 | git add index.js
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/githubocto/repo-visualizer/a999615bdab757559bf94bda1fe6eef232765f85/.npmignore
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 GitHub OCTO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Repo Visualizer
2 |
3 | A GitHub Action that creates an SVG diagram of your repo. Read more [in the writeup](https://octo.github.com/projects/repo-visualization).
4 |
5 | **Please note that this is an experiment. If you have feature requests, please submit a PR or fork and use the code any way you need.**
6 |
7 | For a full demo, check out the [githubocto/repo-visualizer-demo](https://github.com/githubocto/repo-visualizer-demo) repository.
8 |
9 | ## Inputs
10 |
11 | ### `output_file`
12 |
13 | A path (relative to the root of your repo) to where you would like the diagram to live.
14 |
15 | For example: images/diagram.svg
16 |
17 | Default: diagram.svg
18 |
19 | ### `excluded_paths`
20 |
21 | A list of paths to folders to exclude from the diagram, separated by commas.
22 |
23 | For example: dist,node_modules
24 |
25 | Default: node_modules,bower_components,dist,out,build,eject,.next,.netlify,.yarn,.vscode,package-lock.json,yarn.lock
26 |
27 | ### `excluded_globs`
28 |
29 | A semicolon-delimited array of file [globs](https://globster.xyz/) to exclude from the diagram, using [micromatch](https://github.com/micromatch/micromatch) syntax. Provided as an array.
30 |
31 | For example:
32 |
33 | ```yaml
34 | excluded_globs: "frontend/*.spec.js;**/*.{png,jpg};**/!(*.module).ts"
35 | # Guide:
36 | # - 'frontend/*.spec.js' # exclude frontend tests
37 | # - '**/*.{png,ico,md}' # all png, ico, md files in any directory
38 | # - '**/!(*.module).ts' # all TS files except module files
39 | ```
40 |
41 | ### `root_path`
42 |
43 | The directory (and its children) that you want to visualize in the diagram, relative to the repository root.
44 |
45 | For example: `src/`
46 |
47 | Default: `''` (current directory)
48 |
49 | ### `max_depth`
50 |
51 | The maximum number of nested folders to show files within. A higher number will take longer to render.
52 |
53 | Default: 9
54 |
55 | ### `should_push`
56 |
57 | Whether to make a new commit with the diagram and push it to the original repository.
58 |
59 | Should be a boolean value, i.e. `true` or `false`. See `commit_message` and `branch` for how to customise the commit.
60 |
61 | Default: `true`
62 |
63 | ### `commit_message`
64 |
65 | The commit message to use when updating the diagram. Useful for skipping CI. For example: `Updating diagram [skip ci]`
66 |
67 | Default: `Repo visualizer: updated diagram`
68 |
69 | ### `branch`
70 |
71 | The branch name to push the diagram to (branch will be created if it does not yet exist).
72 |
73 | For example: `diagram`
74 |
75 | ### `artifact_name`
76 |
77 | The name of an [artifact](https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts) to create containing the diagram.
78 |
79 | If unspecified, no artifact will be created.
80 |
81 | Default: `''` (no artifact)
82 |
83 | ### `file_colors`
84 |
85 | You can customize the colors for specific file extensions. Key/value pairs will extend the [default colors](https://github.com/githubocto/repo-visualizer/pull/src/language-colors.json).
86 |
87 | For example: '{"js": "red","ts": "green"}'
88 | default: '{}'
89 |
90 | ## Outputs
91 |
92 | ### `svg`
93 |
94 | The contents of the diagram as text. This can be used if you don't want to handle new files.
95 |
96 | ## Example usage
97 |
98 | You'll need to run the `actions/checkout` Action beforehand, to check out the code.
99 |
100 | ```yaml
101 | - name: Checkout code
102 | uses: actions/checkout@master
103 | - name: Update diagram
104 | uses: githubocto/repo-visualizer@0.7.1
105 | with:
106 | output_file: "images/diagram.svg"
107 | excluded_paths: "dist,node_modules"
108 | ```
109 |
110 |
111 | ## Accessing the diagram
112 |
113 | By default, this action will create a new commit with the diagram on the specified branch.
114 |
115 | If you want to avoid new commits, you can create an artifact to accompany the workflow run,
116 | by specifying an `artifact_name`. You can then download the diagram using the
117 | [`actions/download-artifact`](https://github.com/marketplace/actions/download-a-build-artifact)
118 | action from a later step in your workflow,
119 | or by using the [GitHub API](https://docs.github.com/en/rest/reference/actions#artifacts).
120 |
121 | Example:
122 | ```yaml
123 | - name: Update diagram
124 | id: make_diagram
125 | uses: githubocto/repo-visualizer@0.7.1
126 | with:
127 | output_file: "output-diagram.svg"
128 | artifact_name: "my-diagram"
129 | - name: Get artifact
130 | uses: actions/download-artifact@v2
131 | with:
132 | name: "my-diagram"
133 | path: "downloads"
134 | ```
135 | In this example, the diagram will be available at downloads/my-diagram.svg
136 | Note that this will still also create a commit, unless you specify `should_push: false`!
137 |
138 | Alternatively, the SVG description of the diagram is available in the `svg` output,
139 | which you can refer to in your workflow as e.g. `${{ steps.make_diagram.outputs.svg }}`.
140 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: "Repo Visualizer"
2 | description: "A GitHub Action that creates an SVG diagram of your repo"
3 | author: "GitHub OCTO"
4 | inputs:
5 | output_file:
6 | description: "A path (relative to the root of your repo) to where you would like the diagram to live. For example: images/diagram.svg. Default: diagram.svg"
7 | required: false
8 | excluded_paths:
9 | description: "A list of paths to exclude from the diagram, separated by commas. For example: dist,node_modules"
10 | required: false
11 | excluded_globs:
12 | description: "A list of micromatch globs to exclude from the diagram, separated by semicolons. For example: **/*.png;docs/**/*.{png,ico}"
13 | required: false
14 | root_path:
15 | description: 'The directory (and its children) that you want to visualize in the diagram. Default: "" (repository root directory)'
16 | required: false
17 | max_depth:
18 | description: "The maximum number of nested folders to show files within. Default: 9"
19 | required: false
20 | commit_message:
21 | description: "The commit message to use when updating the diagram. Default: Repo visualizer: updated diagram"
22 | required: false
23 | branch:
24 | description: "The branch name to push the diagram to (branch will be created if it does not yet exist). For example: diagram"
25 | required: false
26 | should_push:
27 | description: "Whether to push the new commit back to the repository. Must be true or false. Default: true"
28 | required: false
29 | default: true
30 | artifact_name:
31 | description: "If given, the name of an artifact to be created containing the diagram. Default: don't create an artifact."
32 | required: false
33 | default: ''
34 | file_colors:
35 | description: "You can customize the colors for specific file extensions. Key/value pairs will extend the [default colors](https://github.com/githubocto/repo-visualizer/pull/src/language-colors.json)."
36 | required: false
37 | default: "{}"
38 | outputs:
39 | svg:
40 | description: "The diagram contents as text"
41 | runs:
42 | using: "node16"
43 | main: "index.js"
44 | branding:
45 | color: "purple"
46 | icon: "target"
47 |
--------------------------------------------------------------------------------
/diagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'jsdom',
4 | };
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "build": "node_modules/.bin/esbuild --target=es2019 ./src/index.jsx --bundle --platform=node --outfile=index.js",
4 | "prepare": "husky install",
5 | "typecheck": "yarn run tsc --noEmit --allowJs",
6 | "test:jest": "jest",
7 | "test:watch": "jest --watch",
8 | "test:coverage": "jest --coverage",
9 | "test": "npm run test:jest --"
10 | },
11 | "dependencies": {
12 | "@actions/artifact": "^0.5.2",
13 | "@actions/core": "^1.10.0",
14 | "@actions/exec": "^1.1.0",
15 | "d3": "^7.0.0",
16 | "esbuild": "^0.12.15",
17 | "lodash": "^4.17.21",
18 | "micromatch": "^4.0.4",
19 | "react": "^17.0.2",
20 | "react-dom": "^17.0.2"
21 | },
22 | "devDependencies": {
23 | "@types/jest": "^27.0.1",
24 | "@types/micromatch": "^4.0.2",
25 | "husky": "^7.0.0",
26 | "jest": "^27.0.6",
27 | "ts-jest": "^27.0.4",
28 | "typescript": "^4.3.5"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/CircleText.tsx:
--------------------------------------------------------------------------------
1 | import uniqueId from "lodash/uniqueId";
2 | import React, { useMemo } from "react";
3 |
4 | interface CircleTextProps {
5 | r: number;
6 | rotate?: number;
7 | text: string;
8 | style?: any;
9 | fill?: string;
10 | stroke?: string;
11 | strokeWidth?: string;
12 | }
13 | export const CircleText = ({
14 | r = 10,
15 | rotate = 0,
16 | text = "",
17 | ...props
18 | }: CircleTextProps) => {
19 | const id = useMemo(() => uniqueId("CircleText--"), []);
20 |
21 | return (
22 | <>
23 |
34 |
35 |
36 |
37 | {text}
38 |
39 |
40 | >
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/Tree.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useRef, useState } from "react";
2 | import {
3 | extent,
4 | forceCollide,
5 | forceSimulation,
6 | forceX,
7 | forceY,
8 | hierarchy,
9 | pack,
10 | range,
11 | scaleLinear,
12 | scaleSqrt,
13 | timeFormat,
14 | } from "d3";
15 | import { FileType } from "./types";
16 | import countBy from "lodash/countBy";
17 | import maxBy from "lodash/maxBy";
18 | import entries from "lodash/entries";
19 | import uniqBy from "lodash/uniqBy";
20 | import flatten from "lodash/flatten";
21 | // file colors are from the github/linguist repo
22 | import defaultFileColors from "./language-colors.json";
23 | import { CircleText } from "./CircleText";
24 | import {
25 | keepBetween,
26 | keepCircleInsideCircle,
27 | truncateString,
28 | } from "./utils";
29 |
30 | type Props = {
31 | data: FileType;
32 | filesChanged: string[];
33 | maxDepth: number;
34 | colorEncoding: "type" | "number-of-changes" | "last-change"
35 | customFileColors?: { [key: string]: string };
36 | };
37 | type ExtendedFileType = {
38 | extension?: string;
39 | pathWithoutExtension?: string;
40 | label?: string;
41 | color?: string;
42 | value?: number;
43 | sortOrder?: number;
44 | fileColors?: { [key: string]: string };
45 | } & FileType;
46 | type ProcessedDataItem = {
47 | data: ExtendedFileType;
48 | depth: number;
49 | height: number;
50 | r: number;
51 | x: number;
52 | y: number;
53 | parent: ProcessedDataItem | null;
54 | children: Array;
55 | };
56 | const looseFilesId = "__structure_loose_file__";
57 | const width = 1000;
58 | const height = 1000;
59 | const maxChildren = 9000;
60 | const lastCommitAccessor = (d) => new Date(d.commits?.[0]?.date + "0");
61 | const numberOfCommitsAccessor = (d) => d?.commits?.length || 0;
62 | export const Tree = (
63 | { data, filesChanged = [], maxDepth = 9, colorEncoding = "type", customFileColors}:
64 | Props,
65 | ) => {
66 | const fileColors = { ...defaultFileColors, ...customFileColors };
67 | const [selectedNodeId, setSelectedNodeId] = useState(null);
68 | const cachedPositions = useRef<{ [key: string]: [number, number] }>({});
69 | const cachedOrders = useRef<{ [key: string]: string[] }>({});
70 |
71 | const { colorScale, colorExtent } = useMemo(() => {
72 | if (!data) return { colorScale: () => { }, colorExtent: [0, 0] };
73 | const flattenTree = (d) => {
74 | return d.children ? flatten(d.children.map(flattenTree)) : d;
75 | };
76 | const items = flattenTree(data);
77 | // @ts-ignore
78 | const flatTree = colorEncoding === "last-change"
79 | ? items.map(lastCommitAccessor).sort((a, b) => b - a).slice(0, -8)
80 | : items.map(numberOfCommitsAccessor).sort((a, b) => b - a).slice(2, -2);
81 | const colorExtent = extent(flatTree);
82 |
83 | // const valueScale = scaleLog()
84 | // .domain(colorExtent)
85 | // .range([0, 1])
86 | // .clamp(true);
87 | // const colorScale = scaleSequential((d) => interpolateBuPu(valueScale(d)));
88 | const colors = [
89 | "#f4f4f4",
90 | "#f4f4f4",
91 | "#f4f4f4",
92 | // @ts-ignore
93 | colorEncoding === "last-change" ? "#C7ECEE" : "#FEEAA7",
94 | // @ts-ignore
95 | colorEncoding === "number-of-changes" ? "#3C40C6" : "#823471",
96 | ];
97 | const colorScale = scaleLinear()
98 | .domain(
99 | range(0, colors.length).map((i) => (
100 | +colorExtent[0] +
101 | (colorExtent[1] - colorExtent[0]) * i / (colors.length - 1)
102 | )),
103 | )
104 | .range(colors).clamp(true);
105 | return { colorScale, colorExtent };
106 | }, [data]);
107 |
108 | const getColor = (d) => {
109 | if (colorEncoding === "type") {
110 | const isParent = d.children;
111 | if (isParent) {
112 | const extensions = countBy(d.children, (c) => c.extension);
113 | const mainExtension = maxBy(entries(extensions), ([k, v]) => v)?.[0];
114 | return fileColors[mainExtension] || "#CED6E0";
115 | }
116 | return fileColors[d.extension] || "#CED6E0";
117 | } else if (colorEncoding === "number-of-changes") {
118 | return colorScale(numberOfCommitsAccessor(d)) || "#f4f4f4";
119 | } else if (colorEncoding === "last-change") {
120 | return colorScale(lastCommitAccessor(d)) || "#f4f4f4";
121 | }
122 | };
123 |
124 | const packedData = useMemo(() => {
125 | if (!data) return [];
126 | const hierarchicalData = hierarchy(
127 | processChild(data, getColor, cachedOrders.current, 0, fileColors),
128 | ).sum((d) => d.value)
129 | .sort((a, b) => {
130 | if (b.data.path.startsWith("src/fonts")) {
131 | // a.data.sortOrder,
132 | // b.data.sortOrder,
133 | // (b.data.sortOrder - a.data.sortOrder) ||
134 | // (b.data.name > a.data.name ? 1 : -1),
135 | // a,
136 | // b,
137 | // );
138 | }
139 | return (b.data.sortOrder - a.data.sortOrder) ||
140 | (b.data.name > a.data.name ? 1 : -1);
141 | });
142 |
143 | let packedTree = pack()
144 | .size([width, height * 1.3]) // we'll reflow the tree to be more horizontal, but we want larger bubbles (.pack() sizes the bubbles to fit the space)
145 | .padding((d) => {
146 | if (d.depth <= 0) return 0;
147 | const hasChildWithNoChildren = d.children.filter((d) =>
148 | !d.children?.length
149 | ).length > 1;
150 | if (hasChildWithNoChildren) return 5;
151 | return 13;
152 | // const hasChildren = !!d.children?.find((d) => d?.children?.length);
153 | // return hasChildren ? 60 : 8;
154 | // return [60, 20, 12][d.depth] || 5;
155 | })(hierarchicalData);
156 | packedTree.children = reflowSiblings(
157 | packedTree.children,
158 | cachedPositions.current,
159 | maxDepth,
160 | );
161 | const children = packedTree.descendants() as ProcessedDataItem[];
162 |
163 | cachedOrders.current = {};
164 | cachedPositions.current = {};
165 | const saveCachedPositionForItem = (item) => {
166 | cachedOrders.current[item.data.path] = item.data.sortOrder;
167 | if (item.children) {
168 | item.children.forEach(saveCachedPositionForItem);
169 | }
170 | };
171 | saveCachedPositionForItem(packedTree);
172 | children.forEach((d) => {
173 | cachedPositions.current[d.data.path] = [d.x, d.y];
174 | });
175 |
176 | return children.slice(0, maxChildren);
177 | }, [data, fileColors]);
178 |
179 | const selectedNode = selectedNodeId &&
180 | packedData.find((d) => d.data.path === selectedNodeId);
181 |
182 | const fileTypes = uniqBy(
183 | packedData.map((d) => fileColors[d.data.extension] && d.data.extension),
184 | ).sort().filter(Boolean);
185 |
186 |
187 | return (
188 |
389 | );
390 | };
391 |
392 | const formatD = (d) => (
393 | typeof d === "number" ? d : timeFormat("%b %Y")(d)
394 | );
395 | const ColorLegend = ({ scale, extent, colorEncoding }) => {
396 | if (!scale || !scale.ticks) return null;
397 | const ticks = scale.ticks(10);
398 | return (
399 |
402 |
408 | {/* @ts-ignore */}
409 | {colorEncoding === "number-of-changes" ? "Number of changes" : "Last change date"}
410 |
411 |
412 | {ticks.map((tick, i) => {
413 | const color = scale(tick);
414 | return (
415 |
416 | );
417 | })}
418 |
419 |
420 | {extent.map((d, i) => (
421 |
428 | {formatD(d)}
429 |
430 | ))}
431 |
432 | );
433 | };
434 |
435 | const Legend = ({ fileTypes = [], fileColors}) => {
436 | return (
437 |
441 | {fileTypes.map((extension, i) => (
442 |
443 |
447 |
452 | .{extension}
453 |
454 |
455 | ))}
456 |
464 | each dot sized by file size
465 |
466 |
467 | );
468 | };
469 |
470 | const processChild = (
471 | child: FileType,
472 | getColor,
473 | cachedOrders,
474 | i = 0,
475 | fileColors
476 | ): ExtendedFileType => {
477 | if (!child) return;
478 | const isRoot = !child.path;
479 | let name = child.name;
480 | let path = child.path;
481 | let children = child?.children?.map((c, i) =>
482 | processChild(c, getColor, cachedOrders, i, fileColors)
483 | );
484 | if (children?.length === 1) {
485 | name = `${name}/${children[0].name}`;
486 | path = children[0].path;
487 | children = children[0].children;
488 | }
489 | const pathWithoutExtension = path?.split(".").slice(0, -1).join(".");
490 | const extension = name?.split(".").slice(-1)[0];
491 | const hasExtension = !!fileColors[extension];
492 |
493 | if (isRoot && children) {
494 | const looseChildren = children?.filter((d) => !d.children?.length);
495 | children = [
496 | ...children?.filter((d) => d.children?.length),
497 | {
498 | name: looseFilesId,
499 | path: looseFilesId,
500 | size: 0,
501 | children: looseChildren,
502 | },
503 | ];
504 | }
505 |
506 | let extendedChild = {
507 | ...child,
508 | name,
509 | path,
510 | label: name,
511 | extension,
512 | pathWithoutExtension,
513 |
514 | size:
515 | (["woff", "woff2", "ttf", "otf", "png", "jpg", "svg"].includes(extension)
516 | ? 100
517 | : Math.min(
518 | 15000,
519 | hasExtension ? child.size : Math.min(child.size, 9000),
520 | )) + i, // stupid hack to stabilize circle order/position
521 | value:
522 | (["woff", "woff2", "ttf", "otf", "png", "jpg", "svg"].includes(extension)
523 | ? 100
524 | : Math.min(
525 | 15000,
526 | hasExtension ? child.size : Math.min(child.size, 9000),
527 | )) + i, // stupid hack to stabilize circle order/position
528 | color: "#fff",
529 | children,
530 | } as ExtendedFileType;
531 | extendedChild.color = getColor(extendedChild);
532 | extendedChild.sortOrder = getSortOrder(extendedChild, cachedOrders, i);
533 |
534 | return extendedChild;
535 | };
536 |
537 | const reflowSiblings = (
538 | siblings: ProcessedDataItem[],
539 | cachedPositions: Record = {},
540 | maxDepth: number,
541 | parentRadius?: number,
542 | parentPosition?: [number, number],
543 | ) => {
544 | if (!siblings) return;
545 | let items = [...siblings.map((d) => {
546 | return {
547 | ...d,
548 | x: cachedPositions[d.data.path]?.[0] || d.x,
549 | y: cachedPositions[d.data.path]?.[1] || d.y,
550 | originalX: d.x,
551 | originalY: d.y,
552 | };
553 | })];
554 | const paddingScale = scaleSqrt().domain([maxDepth, 1]).range([3, 8]).clamp(
555 | true,
556 | );
557 | let simulation = forceSimulation(items)
558 | .force(
559 | "centerX",
560 | forceX(width / 2).strength(items[0].depth <= 2 ? 0.01 : 0),
561 | )
562 | .force(
563 | "centerY",
564 | forceY(height / 2).strength(items[0].depth <= 2 ? 0.01 : 0),
565 | )
566 | .force(
567 | "centerX2",
568 | forceX(parentPosition?.[0]).strength(parentPosition ? 0.3 : 0),
569 | )
570 | .force(
571 | "centerY2",
572 | forceY(parentPosition?.[1]).strength(parentPosition ? 0.8 : 0),
573 | )
574 | .force(
575 | "x",
576 | forceX((d) => cachedPositions[d.data.path]?.[0] || width / 2).strength(
577 | (d) =>
578 | cachedPositions[d.data.path]?.[1] ? 0.5 : ((width / height) * 0.3),
579 | ),
580 | )
581 | .force(
582 | "y",
583 | forceY((d) => cachedPositions[d.data.path]?.[1] || height / 2).strength(
584 | (d) =>
585 | cachedPositions[d.data.path]?.[0] ? 0.5 : ((height / width) * 0.3),
586 | ),
587 | )
588 | .force(
589 | "collide",
590 | forceCollide((d) => d.children ? d.r + paddingScale(d.depth) : d.r + 1.6)
591 | .iterations(8).strength(1),
592 | )
593 | .stop();
594 |
595 | for (let i = 0; i < 280; i++) {
596 | simulation.tick();
597 | items.forEach((d) => {
598 | d.x = keepBetween(d.r, d.x, width - d.r);
599 | d.y = keepBetween(d.r, d.y, height - d.r);
600 |
601 | if (parentPosition && parentRadius) {
602 | // keep within radius
603 | const containedPosition = keepCircleInsideCircle(
604 | parentRadius,
605 | parentPosition,
606 | d.r,
607 | [d.x, d.y],
608 | !!d.children?.length,
609 | );
610 | d.x = containedPosition[0];
611 | d.y = containedPosition[1];
612 | }
613 | });
614 | }
615 | // setTimeout(() => simulation.stop(), 100);
616 | const repositionChildren = (d, xDiff, yDiff) => {
617 | let newD = { ...d };
618 | newD.x += xDiff;
619 | newD.y += yDiff;
620 | if (newD.children) {
621 | newD.children = newD.children.map((c) =>
622 | repositionChildren(c, xDiff, yDiff)
623 | );
624 | }
625 | return newD;
626 | };
627 | for (const item of items) {
628 | const itemCachedPosition = cachedPositions[item.data.path] ||
629 | [item.x, item.y];
630 | const itemPositionDiffFromCached = [
631 | item.x - itemCachedPosition[0],
632 | item.y - itemCachedPosition[1],
633 | ];
634 |
635 | if (item.children) {
636 | let repositionedCachedPositions = { ...cachedPositions };
637 | const itemReflowDiff = [
638 | item.x - item.originalX,
639 | item.y - item.originalY,
640 | ];
641 |
642 | item.children = item.children.map((child) =>
643 | repositionChildren(
644 | child,
645 | itemReflowDiff[0],
646 | itemReflowDiff[1],
647 | )
648 | );
649 | if (item.children.length > 4) {
650 | if (item.depth > maxDepth) return;
651 | item.children.forEach((child) => {
652 | // move cached positions with the parent
653 | const childCachedPosition =
654 | repositionedCachedPositions[child.data.path];
655 | if (childCachedPosition) {
656 | repositionedCachedPositions[child.data.path] = [
657 | childCachedPosition[0] + itemPositionDiffFromCached[0],
658 | childCachedPosition[1] + itemPositionDiffFromCached[1],
659 | ];
660 | } else {
661 | // const diff = getPositionFromAngleAndDistance(100, item.r);
662 | repositionedCachedPositions[child.data.path] = [
663 | child.x,
664 | child.y,
665 | ];
666 | }
667 | });
668 | item.children = reflowSiblings(
669 | item.children,
670 | repositionedCachedPositions,
671 | maxDepth,
672 | item.r,
673 | [item.x, item.y],
674 | );
675 | }
676 | }
677 | }
678 | return items;
679 | };
680 |
681 | const getSortOrder = (item: ExtendedFileType, cachedOrders, i = 0) => {
682 | if (cachedOrders[item.path]) return cachedOrders[item.path];
683 | if (cachedOrders[item.path?.split("/")?.slice(0, -1)?.join("/")]) {
684 | return -100000000;
685 | }
686 | if (item.name === "public") return -1000000;
687 | // if (item.depth <= 1 && !item.children) {
688 | // // item.value *= 0.33;
689 | // return item.value * 100;
690 | // }
691 | // if (item.depth <= 1) return -10;
692 | return item.value + -i;
693 | // return b.value - a.value;
694 | };
695 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import { exec } from '@actions/exec'
2 | import * as core from '@actions/core'
3 | import * as artifact from '@actions/artifact'
4 | import React from 'react';
5 | import ReactDOMServer from 'react-dom/server';
6 | import fs from "fs"
7 |
8 | import { processDir } from "./process-dir.js"
9 | import { Tree } from "./Tree.tsx"
10 |
11 | const main = async () => {
12 | core.info('[INFO] Usage https://github.com/githubocto/repo-visualizer#readme')
13 |
14 | core.startGroup('Configuration')
15 | const username = 'repo-visualizer'
16 | await exec('git', ['config', 'user.name', username])
17 | await exec('git', [
18 | 'config',
19 | 'user.email',
20 | `${username}@users.noreply.github.com`,
21 | ])
22 |
23 | core.endGroup()
24 |
25 |
26 | const rootPath = core.getInput("root_path") || ""; // Micro and minimatch do not support paths starting with ./
27 | const maxDepth = core.getInput("max_depth") || 9
28 | const customFileColors = JSON.parse(core.getInput("file_colors") || '{}');
29 | const colorEncoding = core.getInput("color_encoding") || "type"
30 | const commitMessage = core.getInput("commit_message") || "Repo visualizer: update diagram"
31 | const excludedPathsString = core.getInput("excluded_paths") || "node_modules,bower_components,dist,out,build,eject,.next,.netlify,.yarn,.git,.vscode,package-lock.json,yarn.lock"
32 | const excludedPaths = excludedPathsString.split(",").map(str => str.trim())
33 |
34 | // Split on semicolons instead of commas since ',' are allowed in globs, but ';' are not + are not permitted in file/folder names.
35 | const excludedGlobsString = core.getInput('excluded_globs') || '';
36 | const excludedGlobs = excludedGlobsString.split(";");
37 |
38 | const branch = core.getInput("branch")
39 | const data = await processDir(rootPath, excludedPaths, excludedGlobs);
40 |
41 | let doesBranchExist = true
42 |
43 | if (branch) {
44 | await exec('git', ['fetch'])
45 |
46 | try {
47 | await exec('git', ['switch', '-c' , branch,'--track', `origin/${branch}`])
48 | } catch {
49 | doesBranchExist = false
50 | core.info(`Branch ${branch} does not yet exist, creating ${branch}.`)
51 | await exec('git', ['checkout', '-b', branch])
52 | }
53 | }
54 | const componentCodeString = ReactDOMServer.renderToStaticMarkup(
55 |
56 | );
57 |
58 | const outputFile = core.getInput("output_file") || "./diagram.svg"
59 |
60 | core.setOutput('svg', componentCodeString)
61 |
62 | await fs.writeFileSync(outputFile, componentCodeString)
63 |
64 |
65 | await exec('git', ['add', outputFile])
66 | const diff = await execWithOutput('git', ['status', '--porcelain', outputFile])
67 | core.info(`diff: ${diff}`)
68 | if (!diff) {
69 | core.info('[INFO] No changes to the repo detected, exiting')
70 | return
71 | }
72 |
73 | const shouldPush = core.getBooleanInput('should_push')
74 | if (shouldPush) {
75 | core.startGroup('Commit and push diagram')
76 | await exec('git', ['commit', '-m', commitMessage])
77 |
78 | if (doesBranchExist) {
79 | await exec('git', ['push'])
80 | } else {
81 | await exec('git', ['push', '--set-upstream', 'origin', branch])
82 | }
83 |
84 | if (branch) {
85 | await exec('git', 'checkout', '-')
86 | }
87 | core.endGroup()
88 | }
89 |
90 | const shouldUpload = core.getInput('artifact_name') !== ''
91 | if (shouldUpload) {
92 | core.startGroup('Upload diagram to artifacts')
93 | const client = artifact.create()
94 | const result = await client.uploadArtifact(core.getInput('artifact_name'), [outputFile], '.')
95 | if (result.failedItems.length > 0) {
96 | throw 'Artifact was not uploaded successfully.'
97 | }
98 | core.endGroup()
99 | }
100 |
101 | console.log("All set!")
102 | }
103 |
104 | main().catch((e) => {
105 | core.setFailed(e)
106 | })
107 |
108 | function execWithOutput(command, args) {
109 | return new Promise((resolve, reject) => {
110 | try {
111 | exec(command, args, {
112 | listeners: {
113 | stdout: function (res) {
114 | core.info(res.toString())
115 | resolve(res.toString())
116 | },
117 | stderr: function (res) {
118 | core.info(res.toString())
119 | reject(res.toString())
120 | }
121 | }
122 | })
123 | } catch (e) {
124 | reject(e)
125 | }
126 | })
127 | }
128 |
--------------------------------------------------------------------------------
/src/language-colors.json:
--------------------------------------------------------------------------------
1 | {
2 | "bsl": "#814CCC",
3 | "os": "#814CCC",
4 | "4dm": "#004289",
5 | "abap": "#E8274B",
6 | "asddls": "#555e25",
7 | "ash": "#B9D9FF",
8 | "aidl": "#34EB6B",
9 | "al": "#0298c3",
10 | "ampl": "#E6EFBB",
11 | "mod": "#0060ac",
12 | "g4": "#9DC3FF",
13 | "apib": "#2ACCA8",
14 | "apl": "#5A8164",
15 | "dyalog": "#5A8164",
16 | "asax": "#9400ff",
17 | "ascx": "#9400ff",
18 | "ashx": "#9400ff",
19 | "asmx": "#9400ff",
20 | "aspx": "#9400ff",
21 | "axd": "#9400ff",
22 | "dats": "#1ac620",
23 | "hats": "#1ac620",
24 | "sats": "#1ac620",
25 | "as": "#C7D7DC",
26 | "adb": "#02f88c",
27 | "ada": "#02f88c",
28 | "ads": "#02f88c",
29 | "afm": "#fa0f00",
30 | "agda": "#315665",
31 | "als": "#64C800",
32 | "OutJob": "#A89663",
33 | "PcbDoc": "#A89663",
34 | "PrjPCB": "#A89663",
35 | "SchDoc": "#A89663",
36 | "angelscript": "#C7D7DC",
37 | "apacheconf": "#d12127",
38 | "vhost": "#009639",
39 | "cls": "#867db1",
40 | "agc": "#0B3D91",
41 | "applescript": "#101F1F",
42 | "scpt": "#101F1F",
43 | "arc": "#aa2afe",
44 | "asciidoc": "#73a0c5",
45 | "adoc": "#73a0c5",
46 | "aj": "#a957b0",
47 | "asm": "#005daa",
48 | "a51": "#6E4C13",
49 | "inc": "#f69e1d",
50 | "nasm": "#6E4C13",
51 | "astro": "#ff5a03",
52 | "aug": "#9CC134",
53 | "ahk": "#6594b9",
54 | "ahkl": "#6594b9",
55 | "au3": "#1C3552",
56 | "avdl": "#0040FF",
57 | "awk": "#c30e9b",
58 | "auk": "#c30e9b",
59 | "gawk": "#c30e9b",
60 | "mawk": "#c30e9b",
61 | "nawk": "#c30e9b",
62 | "bas": "#867db1",
63 | "bal": "#FF5000",
64 | "bat": "#C1F12E",
65 | "cmd": "#C1F12E",
66 | "bib": "#778899",
67 | "bibtex": "#778899",
68 | "bicep": "#519aba",
69 | "bison": "#6A463F",
70 | "bb": "#00FFAE",
71 | "blade": "#f7523f",
72 | "blade.php": "#f7523f",
73 | "decls": "#00FFAE",
74 | "bmx": "#cd6400",
75 | "bsv": "#12223c",
76 | "boo": "#d4bec1",
77 | "bpl": "#c80fa0",
78 | "brs": "#662D91",
79 | "c": "#555555",
80 | "cats": "#555555",
81 | "h": "#438eff",
82 | "idc": "#555555",
83 | "cs": "#596706",
84 | "cake": "#244776",
85 | "csx": "#178600",
86 | "linq": "#178600",
87 | "cpp": "#f34b7d",
88 | "c++": "#f34b7d",
89 | "cc": "#f34b7d",
90 | "cp": "#B0CE4E",
91 | "cxx": "#f34b7d",
92 | "h++": "#f34b7d",
93 | "hh": "#878787",
94 | "hpp": "#f34b7d",
95 | "hxx": "#f34b7d",
96 | "inl": "#f34b7d",
97 | "ino": "#f34b7d",
98 | "ipp": "#f34b7d",
99 | "re": "#ff5847",
100 | "tcc": "#f34b7d",
101 | "tpp": "#f34b7d",
102 | "clp": "#00A300",
103 | "cmake": "#DA3434",
104 | "cmake.in": "#DA3434",
105 | "dae": "#F1A42B",
106 | "cson": "#244776",
107 | "css": "#563d7c",
108 | "csv": "#237346",
109 | "w": "#5ce600",
110 | "cabal": "#483465",
111 | "capnp": "#c42727",
112 | "ceylon": "#dfa535",
113 | "chpl": "#8dc63f",
114 | "ch": "#403a40",
115 | "ck": "#3f8000",
116 | "cirru": "#ccccff",
117 | "clw": "#db901e",
118 | "asp": "#6a40fd",
119 | "icl": "#3F85AF",
120 | "dcl": "#3F85AF",
121 | "click": "#E4E6F3",
122 | "clj": "#db5855",
123 | "boot": "#db5855",
124 | "cl2": "#db5855",
125 | "cljc": "#db5855",
126 | "cljs": "#db5855",
127 | "cljs.hl": "#db5855",
128 | "cljscm": "#db5855",
129 | "cljx": "#db5855",
130 | "hic": "#db5855",
131 | "soy": "#0d948f",
132 | "ql": "#140f46",
133 | "qll": "#140f46",
134 | "coffee": "#244776",
135 | "_coffee": "#244776",
136 | "cjsx": "#244776",
137 | "iced": "#244776",
138 | "cfm": "#ed2cd6",
139 | "cfml": "#ed2cd6",
140 | "cfc": "#ed2cd6",
141 | "lisp": "#87AED7",
142 | "asd": "#3fb68b",
143 | "cl": "#ed2e2d",
144 | "l": "#ecdebe",
145 | "lsp": "#87AED7",
146 | "ny": "#3fb68b",
147 | "podsl": "#3fb68b",
148 | "sexp": "#3fb68b",
149 | "cwl": "#B5314C",
150 | "cps": "#B0CE4E",
151 | "coq": "#d0b68c",
152 | "v": "#b2b7f8",
153 | "cr": "#000100",
154 | "orc": "#1a1a1a",
155 | "udo": "#1a1a1a",
156 | "csd": "#1a1a1a",
157 | "sco": "#1a1a1a",
158 | "cu": "#3A4E3A",
159 | "cuh": "#3A4E3A",
160 | "pyx": "#fedf5b",
161 | "pxd": "#fedf5b",
162 | "pxi": "#fedf5b",
163 | "d": "#427819",
164 | "di": "#ba595e",
165 | "dm": "#447265",
166 | "dfy": "#FFEC25",
167 | "darcspatch": "#8eff23",
168 | "dpatch": "#8eff23",
169 | "dart": "#00B4AB",
170 | "dwl": "#003a52",
171 | "dhall": "#dfafff",
172 | "dockerfile": "#384d54",
173 | "djs": "#cca760",
174 | "dylan": "#6c616e",
175 | "dyl": "#6c616e",
176 | "intr": "#6c616e",
177 | "lid": "#6c616e",
178 | "E": "#ccce35",
179 | "ecl": "#001d9d",
180 | "eclxml": "#8a1267",
181 | "ejs": "#a91e50",
182 | "ect": "#a91e50",
183 | "jst": "#a91e50",
184 | "eq": "#a78649",
185 | "sch": "#0060ac",
186 | "brd": "#2f4aab",
187 | "eb": "#069406",
188 | "epj": "#913960",
189 | "e": "#4d6977",
190 | "ex": "#6e4a7e",
191 | "exs": "#6e4a7e",
192 | "elm": "#60B5CC",
193 | "el": "#c065db",
194 | "emacs": "#c065db",
195 | "emacs.desktop": "#c065db",
196 | "em": "#FFF4F3",
197 | "emberscript": "#FFF4F3",
198 | "erl": "#B83998",
199 | "app.src": "#B83998",
200 | "es": "#f1e05a",
201 | "escript": "#B83998",
202 | "hrl": "#B83998",
203 | "xrl": "#B83998",
204 | "yrl": "#B83998",
205 | "fs": "#5686a5",
206 | "fsi": "#b845fc",
207 | "fsx": "#b845fc",
208 | "fst": "#572e30",
209 | "flf": "#FFDDBB",
210 | "fx": "#aace60",
211 | "flux": "#88ccff",
212 | "factor": "#636746",
213 | "fy": "#7b9db4",
214 | "fancypack": "#7b9db4",
215 | "fan": "#14253c",
216 | "fnl": "#fff3d7",
217 | "f": "#4d41b1",
218 | "ftl": "#0050b2",
219 | "for": "#4d41b1",
220 | "fth": "#341708",
221 | "4th": "#341708",
222 | "forth": "#341708",
223 | "frt": "#341708",
224 | "f77": "#4d41b1",
225 | "fpp": "#4d41b1",
226 | "f90": "#4d41b1",
227 | "f03": "#4d41b1",
228 | "f08": "#4d41b1",
229 | "f95": "#4d41b1",
230 | "bi": "#867db1",
231 | "fut": "#5f021f",
232 | "g": "#0000cc",
233 | "cnc": "#D08CF2",
234 | "gco": "#D08CF2",
235 | "gcode": "#D08CF2",
236 | "gaml": "#FFC766",
237 | "gms": "#f49a22",
238 | "gap": "#0000cc",
239 | "gd": "#355570",
240 | "gi": "#0000cc",
241 | "tst": "#ca0f21",
242 | "md": "#083fa1",
243 | "ged": "#003058",
244 | "glsl": "#5686a5",
245 | "fp": "#5686a5",
246 | "frag": "#f1e05a",
247 | "frg": "#5686a5",
248 | "fsh": "#5686a5",
249 | "fshader": "#5686a5",
250 | "geo": "#5686a5",
251 | "geom": "#5686a5",
252 | "glslf": "#5686a5",
253 | "glslv": "#5686a5",
254 | "gs": "#f1e05a",
255 | "gshader": "#5686a5",
256 | "shader": "#222c37",
257 | "tesc": "#5686a5",
258 | "tese": "#5686a5",
259 | "vert": "#5686a5",
260 | "vrx": "#5686a5",
261 | "vsh": "#5686a5",
262 | "vshader": "#5686a5",
263 | "gml": "#0060ac",
264 | "kid": "#951531",
265 | "ebuild": "#9400ff",
266 | "eclass": "#9400ff",
267 | "gbr": "#d20b00",
268 | "cmp": "#d20b00",
269 | "gbl": "#d20b00",
270 | "gbo": "#d20b00",
271 | "gbp": "#d20b00",
272 | "gbs": "#d20b00",
273 | "gko": "#d20b00",
274 | "gpb": "#d20b00",
275 | "gpt": "#d20b00",
276 | "gtl": "#d20b00",
277 | "gto": "#d20b00",
278 | "gtp": "#d20b00",
279 | "gts": "#d20b00",
280 | "ncl": "#0060ac",
281 | "sol": "#AA6746",
282 | "feature": "#5B2063",
283 | "story": "#5B2063",
284 | "gitconfig": "#F44D27",
285 | "glf": "#c1ac7f",
286 | "gp": "#f0a9f0",
287 | "gnu": "#f0a9f0",
288 | "gnuplot": "#f0a9f0",
289 | "p": "#5ce600",
290 | "plot": "#f0a9f0",
291 | "plt": "#f0a9f0",
292 | "go": "#00ADD8",
293 | "golo": "#88562A",
294 | "gst": "#0060ac",
295 | "gsx": "#82937f",
296 | "vark": "#82937f",
297 | "grace": "#615f8b",
298 | "gradle": "#02303a",
299 | "gf": "#ff0000",
300 | "graphql": "#e10098",
301 | "gql": "#e10098",
302 | "graphqls": "#e10098",
303 | "dot": "#2596be",
304 | "gv": "#2596be",
305 | "groovy": "#4298b8",
306 | "grt": "#4298b8",
307 | "gtpl": "#4298b8",
308 | "gvy": "#4298b8",
309 | "gsp": "#4298b8",
310 | "cfg": "#d1dbe0",
311 | "workflow": "#0060ac",
312 | "hlsl": "#aace60",
313 | "cginc": "#aace60",
314 | "fxh": "#aace60",
315 | "hlsli": "#aace60",
316 | "html": "#e34c26",
317 | "htm": "#e34c26",
318 | "html.hl": "#e34c26",
319 | "xht": "#e34c26",
320 | "xhtml": "#e34c26",
321 | "ecr": "#2e1052",
322 | "eex": "#6e4a7e",
323 | "html.leex": "#6e4a7e",
324 | "erb": "#701516",
325 | "erb.deface": "#701516",
326 | "rhtml": "#701516",
327 | "phtml": "#4f5d95",
328 | "cshtml": "#512be4",
329 | "razor": "#512be4",
330 | "http": "#005C9C",
331 | "hxml": "#f68712",
332 | "hack": "#878787",
333 | "hhi": "#878787",
334 | "php": "#4F5D95",
335 | "haml": "#ece2a9",
336 | "haml.deface": "#ece2a9",
337 | "handlebars": "#f7931e",
338 | "hbs": "#f7931e",
339 | "hb": "#0e60e3",
340 | "hs": "#5e5086",
341 | "hs-boot": "#5e5086",
342 | "hsc": "#5e5086",
343 | "hx": "#df7900",
344 | "hxsl": "#df7900",
345 | "q": "#0040cd",
346 | "hql": "#dce200",
347 | "hc": "#ffefaf",
348 | "hy": "#7790B2",
349 | "dlm": "#a3522f",
350 | "ipf": "#0000cc",
351 | "ini": "#d1dbe0",
352 | "dof": "#d1dbe0",
353 | "lektorproject": "#d1dbe0",
354 | "prefs": "#d1dbe0",
355 | "properties": "#2A6277",
356 | "idr": "#b30000",
357 | "lidr": "#b30000",
358 | "gitignore": "#000000",
359 | "ijm": "#99AAFF",
360 | "iss": "#264b99",
361 | "isl": "#264b99",
362 | "io": "#a9188d",
363 | "ik": "#078193",
364 | "thy": "#FEFE00",
365 | "ijs": "#9EEDFF",
366 | "flex": "#DBCA00",
367 | "jflex": "#DBCA00",
368 | "json": "#292929",
369 | "avsc": "#292929",
370 | "geojson": "#292929",
371 | "gltf": "#292929",
372 | "har": "#292929",
373 | "ice": "#003fa2",
374 | "JSON-tmLanguage": "#292929",
375 | "jsonl": "#292929",
376 | "mcmeta": "#292929",
377 | "tfstate": "#292929",
378 | "tfstate.backup": "#292929",
379 | "topojson": "#292929",
380 | "webapp": "#292929",
381 | "webmanifest": "#292929",
382 | "yy": "#4B6C4B",
383 | "yyp": "#292929",
384 | "jsonc": "#292929",
385 | "sublime-build": "#292929",
386 | "sublime-commands": "#292929",
387 | "sublime-completions": "#292929",
388 | "sublime-keymap": "#292929",
389 | "sublime-macro": "#292929",
390 | "sublime-menu": "#292929",
391 | "sublime-mousemap": "#292929",
392 | "sublime-project": "#292929",
393 | "sublime-settings": "#292929",
394 | "sublime-theme": "#292929",
395 | "sublime-workspace": "#292929",
396 | "sublime_metrics": "#292929",
397 | "sublime_session": "#292929",
398 | "json5": "#267CB9",
399 | "jsonld": "#0c479c",
400 | "jq": "#c7254e",
401 | "j": "#ff0c5a",
402 | "java": "#b07219",
403 | "jav": "#b07219",
404 | "jsp": "#2A6277",
405 | "js": "#f1e05a",
406 | "_js": "#f1e05a",
407 | "bones": "#f1e05a",
408 | "cjs": "#f1e05a",
409 | "es6": "#f1e05a",
410 | "jake": "#f1e05a",
411 | "javascript": "#f1e05a",
412 | "jsb": "#f1e05a",
413 | "jscad": "#f1e05a",
414 | "jsfl": "#f1e05a",
415 | "jsm": "#f1e05a",
416 | "jss": "#f1e05a",
417 | "jsx": "#f1e05a",
418 | "mjs": "#f1e05a",
419 | "njs": "#f1e05a",
420 | "pac": "#f1e05a",
421 | "sjs": "#f1e05a",
422 | "ssjs": "#f1e05a",
423 | "xsjs": "#f1e05a",
424 | "xsjslib": "#f1e05a",
425 | "js.erb": "#f1e05a",
426 | "jinja": "#a52a22",
427 | "j2": "#a52a22",
428 | "jinja2": "#a52a22",
429 | "jison": "#56b3cb",
430 | "jisonlex": "#56b3cb",
431 | "ol": "#843179",
432 | "iol": "#843179",
433 | "jsonnet": "#0064bd",
434 | "libsonnet": "#0064bd",
435 | "jl": "#a270ba",
436 | "ipynb": "#DA5B0B",
437 | "krl": "#28430A",
438 | "ksy": "#773b37",
439 | "kak": "#6f8042",
440 | "kicad_pcb": "#2f4aab",
441 | "kicad_mod": "#2f4aab",
442 | "kicad_wks": "#2f4aab",
443 | "kt": "#A97BFF",
444 | "ktm": "#A97BFF",
445 | "kts": "#A97BFF",
446 | "csl": "#0060ac",
447 | "lfe": "#4C3023",
448 | "ll": "#185619",
449 | "lol": "#cc9900",
450 | "lsl": "#3d9970",
451 | "lslp": "#3d9970",
452 | "lvproj": "#fede06",
453 | "lvlib": "#fede06",
454 | "lark": "#2980B9",
455 | "lasso": "#999999",
456 | "las": "#999999",
457 | "lasso8": "#999999",
458 | "lasso9": "#999999",
459 | "latte": "#f2a542",
460 | "less": "#1d365d",
461 | "lex": "#DBCA00",
462 | "ly": "#9ccc7c",
463 | "ily": "#9ccc7c",
464 | "m": "#438eff",
465 | "liquid": "#67b8de",
466 | "lagda": "#315665",
467 | "litcoffee": "#244776",
468 | "coffee.md": "#244776",
469 | "lhs": "#5e5086",
470 | "_ls": "#499886",
471 | "lgt": "#295b9a",
472 | "logtalk": "#295b9a",
473 | "lookml": "#652B81",
474 | "model.lkml": "#652B81",
475 | "view.lkml": "#652B81",
476 | "lua": "#000080",
477 | "fcgi": "#89e051",
478 | "nse": "#000080",
479 | "p8": "#000080",
480 | "pd_lua": "#000080",
481 | "rbxs": "#000080",
482 | "rockspec": "#000080",
483 | "wlua": "#000080",
484 | "matlab": "#e16737",
485 | "mcr": "#00a6a6",
486 | "mlir": "#5EC8DB",
487 | "mq4": "#62A8D6",
488 | "mqh": "#4A76B8",
489 | "mq5": "#4A76B8",
490 | "mtml": "#b7e1f4",
491 | "m2": "#d8ffff",
492 | "mak": "#427819",
493 | "make": "#427819",
494 | "mk": "#427819",
495 | "mkfile": "#427819",
496 | "mako": "#7e858d",
497 | "mao": "#7e858d",
498 | "markdown": "#083fa1",
499 | "mdown": "#083fa1",
500 | "mdwn": "#083fa1",
501 | "mdx": "#083fa1",
502 | "mkd": "#083fa1",
503 | "mkdn": "#083fa1",
504 | "mkdown": "#083fa1",
505 | "ronn": "#083fa1",
506 | "scd": "#46390b",
507 | "workbook": "#083fa1",
508 | "marko": "#42bff2",
509 | "mask": "#222c37",
510 | "mathematica": "#dd1100",
511 | "cdf": "#dd1100",
512 | "ma": "#dd1100",
513 | "mt": "#dd1100",
514 | "nbp": "#dd1100",
515 | "wl": "#dd1100",
516 | "wlt": "#dd1100",
517 | "maxpat": "#c4a79c",
518 | "maxhelp": "#c4a79c",
519 | "maxproj": "#c4a79c",
520 | "mxt": "#c4a79c",
521 | "pat": "#c4a79c",
522 | "metal": "#8f14e9",
523 | "druby": "#c7a938",
524 | "duby": "#c7a938",
525 | "mirah": "#c7a938",
526 | "mo": "#de1d31",
527 | "i3": "#223388",
528 | "ig": "#223388",
529 | "m3": "#223388",
530 | "mg": "#223388",
531 | "moon": "#ff4585",
532 | "x68": "#005daa",
533 | "mustache": "#724b3b",
534 | "nl": "#87AED7",
535 | "nss": "#111522",
536 | "ne": "#990000",
537 | "nearley": "#990000",
538 | "n": "#ecdebe",
539 | "axs": "#0aa0ff",
540 | "axi": "#0aa0ff",
541 | "axs.erb": "#747faa",
542 | "axi.erb": "#747faa",
543 | "nlogo": "#ff6375",
544 | "nf": "#3ac486",
545 | "nginx": "#009639",
546 | "nginxconf": "#009639",
547 | "nim": "#ffc200",
548 | "nim.cfg": "#ffc200",
549 | "nimble": "#ffc200",
550 | "nimrod": "#ffc200",
551 | "nims": "#ffc200",
552 | "nit": "#009917",
553 | "nix": "#7e7eff",
554 | "nu": "#c9df40",
555 | "numpy": "#9C8AF9",
556 | "numpyw": "#9C8AF9",
557 | "numsc": "#9C8AF9",
558 | "njk": "#3d8137",
559 | "ml": "#dc566d",
560 | "eliom": "#3be133",
561 | "eliomi": "#3be133",
562 | "ml4": "#3be133",
563 | "mli": "#3be133",
564 | "mll": "#3be133",
565 | "mly": "#3be133",
566 | "odin": "#60AFFE",
567 | "mm": "#0060ac",
568 | "sj": "#ff0c5a",
569 | "omgrofl": "#cabbff",
570 | "opal": "#f7ede0",
571 | "rego": "#7d9199",
572 | "opencl": "#ed2e2d",
573 | "qasm": "#AA70FF",
574 | "scad": "#e5cd45",
575 | "plist": "#0060ac",
576 | "org": "#77aa99",
577 | "oxygene": "#cdd0e3",
578 | "oz": "#fab738",
579 | "p4": "#7055b5",
580 | "pegjs": "#234d6b",
581 | "aw": "#4F5D95",
582 | "ctp": "#4F5D95",
583 | "php3": "#4F5D95",
584 | "php4": "#4F5D95",
585 | "php5": "#4F5D95",
586 | "phps": "#4F5D95",
587 | "phpt": "#4F5D95",
588 | "pls": "#dad8d8",
589 | "bdy": "#dad8d8",
590 | "ddl": "#e38c00",
591 | "fnc": "#dad8d8",
592 | "pck": "#dad8d8",
593 | "pkb": "#dad8d8",
594 | "pks": "#dad8d8",
595 | "plb": "#dad8d8",
596 | "plsql": "#dad8d8",
597 | "prc": "#e38c00",
598 | "spc": "#dad8d8",
599 | "sql": "#e38c00",
600 | "tpb": "#dad8d8",
601 | "tps": "#dad8d8",
602 | "trg": "#dad8d8",
603 | "vw": "#dad8d8",
604 | "pgsql": "#336790",
605 | "pov": "#6bac65",
606 | "pan": "#cc0000",
607 | "psc": "#6600cc",
608 | "parrot": "#f3ca0a",
609 | "pas": "#E3F171",
610 | "dfm": "#E3F171",
611 | "dpr": "#E3F171",
612 | "lpr": "#E3F171",
613 | "pascal": "#E3F171",
614 | "pp": "#302B6D",
615 | "pwn": "#dbb284",
616 | "sma": "#dbb284",
617 | "pep": "#C76F5B",
618 | "pl": "#0000fb",
619 | "cgi": "#89e051",
620 | "perl": "#0298c3",
621 | "ph": "#0298c3",
622 | "plx": "#0298c3",
623 | "psgi": "#0298c3",
624 | "t": "#cf142b",
625 | "pig": "#fcd7de",
626 | "pike": "#005390",
627 | "pmod": "#005390",
628 | "pogo": "#d80074",
629 | "pcss": "#dc3a0c",
630 | "postcss": "#dc3a0c",
631 | "ps": "#da291c",
632 | "eps": "#da291c",
633 | "epsi": "#da291c",
634 | "pfa": "#da291c",
635 | "pbt": "#8f0f8d",
636 | "sra": "#8f0f8d",
637 | "sru": "#8f0f8d",
638 | "srw": "#8f0f8d",
639 | "ps1": "#012456",
640 | "psd1": "#012456",
641 | "psm1": "#012456",
642 | "prisma": "#0c344b",
643 | "pde": "#0096D8",
644 | "prolog": "#74283c",
645 | "yap": "#74283c",
646 | "spin": "#7fa2a7",
647 | "jade": "#a86454",
648 | "pug": "#a86454",
649 | "pb": "#5a6986",
650 | "pbi": "#5a6986",
651 | "purs": "#1D222D",
652 | "py": "#3572A5",
653 | "gyp": "#3572A5",
654 | "gypi": "#3572A5",
655 | "lmi": "#3572A5",
656 | "py3": "#3572A5",
657 | "pyde": "#3572A5",
658 | "pyi": "#3572A5",
659 | "pyp": "#3572A5",
660 | "pyt": "#3572A5",
661 | "pyw": "#3572A5",
662 | "rpy": "#ff7f7f",
663 | "smk": "#3572A5",
664 | "spec": "#701516",
665 | "tac": "#3572A5",
666 | "wsgi": "#3572A5",
667 | "xpy": "#3572A5",
668 | "pytb": "#3572A5",
669 | "qs": "#00b841",
670 | "qml": "#44a51c",
671 | "qbs": "#44a51c",
672 | "r": "#358a5b",
673 | "rd": "#198CE7",
674 | "rsx": "#198CE7",
675 | "raml": "#77d9fb",
676 | "rdoc": "#701516",
677 | "rexx": "#d90e09",
678 | "pprx": "#d90e09",
679 | "rex": "#d90e09",
680 | "rmd": "#198ce7",
681 | "rnh": "#665a4e",
682 | "rno": "#ecdebe",
683 | "rkt": "#3c5caa",
684 | "rktd": "#3c5caa",
685 | "rktl": "#3c5caa",
686 | "scrbl": "#3c5caa",
687 | "rl": "#9d5200",
688 | "6pl": "#0000fb",
689 | "6pm": "#0000fb",
690 | "nqp": "#0000fb",
691 | "p6": "#0000fb",
692 | "p6l": "#0000fb",
693 | "p6m": "#0000fb",
694 | "pl6": "#0000fb",
695 | "pm6": "#0000fb",
696 | "raku": "#0000fb",
697 | "rakumod": "#0000fb",
698 | "rsc": "#fffaa0",
699 | "res": "#0060ac",
700 | "rei": "#ff5847",
701 | "reb": "#358a5b",
702 | "r2": "#358a5b",
703 | "r3": "#358a5b",
704 | "rebol": "#358a5b",
705 | "red": "#f50000",
706 | "reds": "#f50000",
707 | "regexp": "#009a00",
708 | "regex": "#009a00",
709 | "rs": "#0060ac",
710 | "ring": "#2D54CB",
711 | "riot": "#A71E49",
712 | "robot": "#00c0b5",
713 | "roff": "#ecdebe",
714 | "1": "#ecdebe",
715 | "1in": "#ecdebe",
716 | "1m": "#ecdebe",
717 | "1x": "#ecdebe",
718 | "2": "#ecdebe",
719 | "3": "#ecdebe",
720 | "3in": "#ecdebe",
721 | "3m": "#ecdebe",
722 | "3p": "#ecdebe",
723 | "3pm": "#ecdebe",
724 | "3qt": "#ecdebe",
725 | "3x": "#ecdebe",
726 | "4": "#ecdebe",
727 | "5": "#ecdebe",
728 | "6": "#ecdebe",
729 | "7": "#ecdebe",
730 | "8": "#ecdebe",
731 | "9": "#ecdebe",
732 | "man": "#ecdebe",
733 | "mdoc": "#ecdebe",
734 | "me": "#ecdebe",
735 | "nr": "#ecdebe",
736 | "tmac": "#ecdebe",
737 | "rg": "#cc0088",
738 | "rb": "#701516",
739 | "builder": "#701516",
740 | "eye": "#701516",
741 | "gemspec": "#701516",
742 | "god": "#701516",
743 | "jbuilder": "#701516",
744 | "mspec": "#701516",
745 | "pluginspec": "#0060ac",
746 | "podspec": "#701516",
747 | "prawn": "#701516",
748 | "rabl": "#701516",
749 | "rake": "#701516",
750 | "rbi": "#701516",
751 | "rbuild": "#701516",
752 | "rbw": "#701516",
753 | "rbx": "#701516",
754 | "ru": "#701516",
755 | "ruby": "#701516",
756 | "thor": "#701516",
757 | "watchr": "#701516",
758 | "rs.in": "#dea584",
759 | "sas": "#B34936",
760 | "scss": "#c6538c",
761 | "sparql": "#0C4597",
762 | "rq": "#0C4597",
763 | "sqf": "#3F3F3F",
764 | "hqf": "#3F3F3F",
765 | "cql": "#e38c00",
766 | "mysql": "#e38c00",
767 | "tab": "#e38c00",
768 | "udf": "#e38c00",
769 | "viw": "#e38c00",
770 | "db2": "#e38c00",
771 | "srt": "#9e0101",
772 | "svg": "#ff9900",
773 | "sls": "#1e4aec",
774 | "sass": "#a53b70",
775 | "scala": "#c22d40",
776 | "kojo": "#c22d40",
777 | "sbt": "#c22d40",
778 | "sc": "#46390b",
779 | "scaml": "#bd181a",
780 | "scm": "#1e4aec",
781 | "sld": "#1e4aec",
782 | "sps": "#1e4aec",
783 | "ss": "#1e4aec",
784 | "sci": "#ca0f21",
785 | "sce": "#ca0f21",
786 | "self": "#0579aa",
787 | "sh": "#89e051",
788 | "bash": "#89e051",
789 | "bats": "#89e051",
790 | "command": "#89e051",
791 | "env": "#89e051",
792 | "ksh": "#89e051",
793 | "sh.in": "#89e051",
794 | "tmux": "#89e051",
795 | "tool": "#89e051",
796 | "zsh": "#89e051",
797 | "shen": "#120F14",
798 | "sl": "#007eff",
799 | "slim": "#2b2b2b",
800 | "cocci": "#c94949",
801 | "st": "#3fb34f",
802 | "tpl": "#f0c040",
803 | "sp": "#f69e1d",
804 | "nut": "#800000",
805 | "stan": "#b2011d",
806 | "fun": "#dc566d",
807 | "sig": "#dc566d",
808 | "sml": "#dc566d",
809 | "bzl": "#76d275",
810 | "do": "#1a5f91",
811 | "ado": "#1a5f91",
812 | "doh": "#1a5f91",
813 | "ihlp": "#1a5f91",
814 | "mata": "#1a5f91",
815 | "matah": "#1a5f91",
816 | "sthlp": "#1a5f91",
817 | "styl": "#ff6347",
818 | "sss": "#2fcc9f",
819 | "svelte": "#ff3e00",
820 | "swift": "#F05138",
821 | "sv": "#DAE1C2",
822 | "svh": "#DAE1C2",
823 | "vh": "#DAE1C2",
824 | "8xp": "#A0AA87",
825 | "8xk": "#A0AA87",
826 | "8xk.txt": "#A0AA87",
827 | "8xp.txt": "#A0AA87",
828 | "tla": "#4b0079",
829 | "toml": "#9c4221",
830 | "tsv": "#237346",
831 | "tsx": "#0060ac",
832 | "txl": "#0178b8",
833 | "tcl": "#e4cc98",
834 | "adp": "#e4cc98",
835 | "tm": "#e4cc98",
836 | "tex": "#3D6117",
837 | "aux": "#3D6117",
838 | "bbx": "#3D6117",
839 | "cbx": "#3D6117",
840 | "dtx": "#3D6117",
841 | "ins": "#3D6117",
842 | "lbx": "#3D6117",
843 | "ltx": "#3D6117",
844 | "mkii": "#3D6117",
845 | "mkiv": "#3D6117",
846 | "mkvi": "#3D6117",
847 | "sty": "#3D6117",
848 | "toc": "#f7e43f",
849 | "txt": "#199f4b",
850 | "textile": "#ffe7ac",
851 | "thrift": "#D12127",
852 | "tu": "#cf142b",
853 | "twig": "#c1d026",
854 | "ts": "#0060ac",
855 | "upc": "#4e3617",
856 | "anim": "#222c37",
857 | "asset": "#222c37",
858 | "mat": "#222c37",
859 | "meta": "#222c37",
860 | "prefab": "#222c37",
861 | "unity": "#222c37",
862 | "uno": "#9933cc",
863 | "uc": "#a54c4d",
864 | "ur": "#ccccee",
865 | "urs": "#ccccee",
866 | "frm": "#867db1",
867 | "frx": "#867db1",
868 | "vba": "#199f4b",
869 | "vbs": "#15dcdc",
870 | "vcl": "#148AA8",
871 | "vhdl": "#adb2cb",
872 | "vhd": "#adb2cb",
873 | "vhf": "#adb2cb",
874 | "vhi": "#adb2cb",
875 | "vho": "#adb2cb",
876 | "vhs": "#adb2cb",
877 | "vht": "#adb2cb",
878 | "vhw": "#adb2cb",
879 | "vala": "#fbe5cd",
880 | "vapi": "#fbe5cd",
881 | "vdf": "#f26025",
882 | "veo": "#b2b7f8",
883 | "snip": "#199f4b",
884 | "snippet": "#199f4b",
885 | "snippets": "#199f4b",
886 | "vim": "#199f4b",
887 | "vmb": "#199f4b",
888 | "vb": "#945db7",
889 | "vbhtml": "#945db7",
890 | "volt": "#1F1F1F",
891 | "vue": "#41b883",
892 | "owl": "#5b70bd",
893 | "wast": "#04133b",
894 | "wat": "#04133b",
895 | "mediawiki": "#fc5757",
896 | "wiki": "#fc5757",
897 | "wikitext": "#fc5757",
898 | "reg": "#52d5ff",
899 | "wlk": "#a23738",
900 | "x10": "#4B6BEF",
901 | "xc": "#99DA07",
902 | "xml": "#0060ac",
903 | "adml": "#0060ac",
904 | "admx": "#0060ac",
905 | "ant": "#0060ac",
906 | "axml": "#0060ac",
907 | "builds": "#0060ac",
908 | "ccproj": "#0060ac",
909 | "ccxml": "#0060ac",
910 | "clixml": "#0060ac",
911 | "cproject": "#0060ac",
912 | "cscfg": "#0060ac",
913 | "csdef": "#0060ac",
914 | "csproj": "#0060ac",
915 | "ct": "#0060ac",
916 | "depproj": "#0060ac",
917 | "dita": "#0060ac",
918 | "ditamap": "#0060ac",
919 | "ditaval": "#0060ac",
920 | "dll.config": "#0060ac",
921 | "dotsettings": "#0060ac",
922 | "filters": "#0060ac",
923 | "fsproj": "#0060ac",
924 | "fxml": "#0060ac",
925 | "glade": "#0060ac",
926 | "gmx": "#0060ac",
927 | "grxml": "#0060ac",
928 | "iml": "#0060ac",
929 | "ivy": "#0060ac",
930 | "jelly": "#0060ac",
931 | "jsproj": "#0060ac",
932 | "kml": "#0060ac",
933 | "launch": "#0060ac",
934 | "mdpolicy": "#0060ac",
935 | "mjml": "#0060ac",
936 | "mxml": "#0060ac",
937 | "natvis": "#0060ac",
938 | "ndproj": "#0060ac",
939 | "nproj": "#0060ac",
940 | "nuspec": "#0060ac",
941 | "odd": "#0060ac",
942 | "osm": "#0060ac",
943 | "pkgproj": "#0060ac",
944 | "proj": "#0060ac",
945 | "props": "#0060ac",
946 | "ps1xml": "#0060ac",
947 | "psc1": "#0060ac",
948 | "pt": "#0060ac",
949 | "rdf": "#0060ac",
950 | "resx": "#0060ac",
951 | "rss": "#0060ac",
952 | "scxml": "#0060ac",
953 | "sfproj": "#0060ac",
954 | "shproj": "#0060ac",
955 | "srdf": "#0060ac",
956 | "storyboard": "#0060ac",
957 | "sublime-snippet": "#0060ac",
958 | "targets": "#0060ac",
959 | "tml": "#0060ac",
960 | "ui": "#0060ac",
961 | "urdf": "#0060ac",
962 | "ux": "#0060ac",
963 | "vbproj": "#0060ac",
964 | "vcxproj": "#0060ac",
965 | "vsixmanifest": "#0060ac",
966 | "vssettings": "#0060ac",
967 | "vstemplate": "#0060ac",
968 | "vxml": "#0060ac",
969 | "wixproj": "#0060ac",
970 | "wsdl": "#0060ac",
971 | "wsf": "#0060ac",
972 | "wxi": "#0060ac",
973 | "wxl": "#0060ac",
974 | "wxs": "#0060ac",
975 | "x3d": "#0060ac",
976 | "xacro": "#0060ac",
977 | "xaml": "#0060ac",
978 | "xib": "#0060ac",
979 | "xlf": "#0060ac",
980 | "xliff": "#0060ac",
981 | "xmi": "#0060ac",
982 | "xml.dist": "#0060ac",
983 | "xmp": "#0060ac",
984 | "xproj": "#0060ac",
985 | "xsd": "#0060ac",
986 | "xspec": "#0060ac",
987 | "xul": "#0060ac",
988 | "zcml": "#0060ac",
989 | "stTheme": "#0060ac",
990 | "tmCommand": "#0060ac",
991 | "tmLanguage": "#0060ac",
992 | "tmPreferences": "#0060ac",
993 | "tmSnippet": "#0060ac",
994 | "tmTheme": "#0060ac",
995 | "xquery": "#5232e7",
996 | "xq": "#5232e7",
997 | "xql": "#5232e7",
998 | "xqm": "#5232e7",
999 | "xqy": "#5232e7",
1000 | "xslt": "#EB8CEB",
1001 | "xsl": "#EB8CEB",
1002 | "xojo_code": "#81bd41",
1003 | "xojo_menu": "#81bd41",
1004 | "xojo_report": "#81bd41",
1005 | "xojo_script": "#81bd41",
1006 | "xojo_toolbar": "#81bd41",
1007 | "xojo_window": "#81bd41",
1008 | "xsh": "#285EEF",
1009 | "xtend": "#24255d",
1010 | "yml": "#cb171e",
1011 | "mir": "#cb171e",
1012 | "reek": "#cb171e",
1013 | "rviz": "#cb171e",
1014 | "sublime-syntax": "#cb171e",
1015 | "syntax": "#cb171e",
1016 | "yaml": "#cb171e",
1017 | "yaml-tmlanguage": "#cb171e",
1018 | "yaml.sed": "#cb171e",
1019 | "yml.mysql": "#cb171e",
1020 | "yar": "#220000",
1021 | "yara": "#220000",
1022 | "yasnippet": "#32AB90",
1023 | "y": "#4B6C4B",
1024 | "yacc": "#4B6C4B",
1025 | "zap": "#0d665e",
1026 | "xzap": "#0d665e",
1027 | "zil": "#dc75e5",
1028 | "mud": "#dc75e5",
1029 | "zs": "#00BCD1",
1030 | "zep": "#118f9e",
1031 | "zig": "#ec915c",
1032 | "zimpl": "#d67711",
1033 | "zmpl": "#d67711",
1034 | "zpl": "#d67711",
1035 | "ec": "#913960",
1036 | "eh": "#913960",
1037 | "fish": "#4aae47",
1038 | "mrc": "#3d57c3",
1039 | "mcfunction": "#E22837",
1040 | "mu": "#244963",
1041 | "nanorc": "#2d004d",
1042 | "nc": "#94B0C7",
1043 | "ooc": "#b0b77e",
1044 | "rst": "#141414",
1045 | "rest": "#141414",
1046 | "rest.txt": "#141414",
1047 | "rst.txt": "#141414",
1048 | "sed": "#64b970",
1049 | "wdl": "#42f1f4",
1050 | "wisp": "#7582D1",
1051 | "prg": "#403a40",
1052 | "prw": "#403a40"
1053 | }
--------------------------------------------------------------------------------
/src/process-dir.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import * as nodePath from 'path';
3 | import { shouldExcludePath } from './should-exclude-path';
4 |
5 |
6 | export const processDir = async (rootPath = "", excludedPaths = [], excludedGlobs = []) => {
7 | const foldersToIgnore = [".git", ...excludedPaths]
8 | const fullPathFoldersToIgnore = new Set(foldersToIgnore.map((d) =>
9 | nodePath.join(rootPath, d)
10 | ));
11 |
12 |
13 | const getFileStats = async (path = "") => {
14 | const stats = await fs.statSync(`./${path}`);
15 | const name = path.split("/").filter(Boolean).slice(-1)[0];
16 | const size = stats.size;
17 | const relativePath = path.slice(rootPath.length + 1);
18 | return {
19 | name,
20 | path: relativePath,
21 | size,
22 | };
23 | };
24 | const addItemToTree = async (
25 | path = "",
26 | isFolder = true,
27 | ) => {
28 | try {
29 | console.log("Looking in ", `./${path}`);
30 |
31 | if (isFolder) {
32 | const filesOrFolders = await fs.readdirSync(`./${path}`);
33 | const children = [];
34 |
35 | for (const fileOrFolder of filesOrFolders) {
36 | const fullPath = nodePath.join(path, fileOrFolder);
37 | if (shouldExcludePath(fullPath, fullPathFoldersToIgnore, excludedGlobs)) {
38 | continue;
39 | }
40 |
41 | const info = fs.statSync(`./${fullPath}`);
42 | const stats = await addItemToTree(
43 | fullPath,
44 | info.isDirectory(),
45 | );
46 | if (stats) children.push(stats);
47 | }
48 |
49 | const stats = await getFileStats(path);
50 | return { ...stats, children };
51 | }
52 |
53 | if (shouldExcludePath(path, fullPathFoldersToIgnore, excludedGlobs)) {
54 | return null;
55 | }
56 | const stats = getFileStats(path);
57 | return stats;
58 |
59 | } catch (e) {
60 | console.log("Issue trying to read file", path, e);
61 | return null;
62 | }
63 | };
64 |
65 | const tree = await addItemToTree(rootPath);
66 |
67 | return tree;
68 | };
69 |
--------------------------------------------------------------------------------
/src/should-exclude-path.test.ts:
--------------------------------------------------------------------------------
1 | import { shouldExcludePath } from './should-exclude-path';
2 |
3 | describe("shouldExcludePath", () => {
4 |
5 | it("excludes based on folder or perfect match relative to root", () => {
6 | const excludePaths = new Set([
7 | 'node_modules/',
8 | 'yarn.lock'
9 | ]);
10 | const excludeGlobs: string[] = [];
11 |
12 | const testShouldExcludePath = (path: string) => shouldExcludePath(path, excludePaths, excludeGlobs);
13 |
14 | expect(testShouldExcludePath('node_modules/')).toEqual(true);
15 | expect(testShouldExcludePath('yarn.lock')).toEqual(true);
16 |
17 | // Non-matched files work
18 | expect(testShouldExcludePath('src/app.js')).toEqual(false);
19 | expect(testShouldExcludePath('src/yarn.lock')).toEqual(false);
20 | });
21 |
22 | it("excludes based on micromatch globs", () => {
23 | const excludePaths = new Set();
24 | const excludeGlobs = [
25 | 'node_modules/**', // exclude same items as paths
26 | '**/yarn.lock', // avoid all yarn.locks
27 | '**/*.png', // file extension block
28 | '**/!(*.module).ts' // Negation: block non-module files, not regular ones
29 | ]
30 |
31 | const testShouldExcludePath = (path: string) => shouldExcludePath(path, excludePaths, excludeGlobs);
32 |
33 | expect(testShouldExcludePath('node_modules/jest/index.js')).toEqual(true);
34 | expect(testShouldExcludePath('node_modules/jest')).toEqual(true);
35 |
36 | // Block all nested lockfiles
37 | expect(testShouldExcludePath('yarn.lock')).toEqual(true);
38 | expect(testShouldExcludePath('subpackage/yarn.lock')).toEqual(true);
39 |
40 | // Block by file extension
41 | expect(testShouldExcludePath('src/docs/boo.png')).toEqual(true);
42 | expect(testShouldExcludePath('test/boo.png')).toEqual(true);
43 | expect(testShouldExcludePath('boo.png')).toEqual(true);
44 |
45 | // Block TS files unless they are modules
46 | expect(testShouldExcludePath('index.ts')).toEqual(true);
47 | expect(testShouldExcludePath('index.module.ts')).toEqual(false);
48 |
49 | // Regular files work
50 | expect(testShouldExcludePath('src/index.js')).toEqual(false);
51 |
52 |
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/src/should-exclude-path.ts:
--------------------------------------------------------------------------------
1 | import { isMatch } from "micromatch";
2 |
3 | /**
4 | * True if path is excluded by either the path or glob criteria.
5 | * path may be to a directory or individual file.
6 | */
7 | export const shouldExcludePath = (
8 | path: string,
9 | pathsToIgnore: Set,
10 | globsToIgnore: string[]
11 | ): boolean => {
12 | if (!path) return false;
13 |
14 | return (
15 | pathsToIgnore.has(path) ||
16 | globsToIgnore.some(
17 | (glob) =>
18 | glob &&
19 | isMatch(processPath(path), glob, {
20 | dot: true,
21 | })
22 | )
23 | );
24 | };
25 |
26 | const processPath = (path: string): string => {
27 | if (path.startsWith("./")) return path.substring(2);
28 | return path;
29 | };
30 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export type ImportType = {
2 | moduleName: string;
3 | defaultImport: string;
4 | namedImports: Record[];
5 | starImport: string;
6 | sideEffectOnly: boolean;
7 | };
8 | export type CommitType = {
9 | hash: string;
10 | subject: string;
11 | author: string;
12 | date: string;
13 | diff: { added: number; removed: number; modified: number };
14 | };
15 | export type FileType = {
16 | name: string;
17 | path: string;
18 | size: number;
19 | commits?: CommitType[];
20 | imports?: ImportType[];
21 | numberOfLines?: number;
22 | children?: FileType[];
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export const truncateString = (
2 | string: string = "",
3 | length: number = 20,
4 | ): string => {
5 | return string.length > length + 3
6 | ? string.substring(0, length) + "..."
7 | : string;
8 | };
9 |
10 | export const keepBetween = (min: number, max: number, value: number) => {
11 | return Math.max(min, Math.min(max, value));
12 | };
13 |
14 | export const getPositionFromAngleAndDistance = (
15 | angle: number,
16 | distance: number,
17 | ): [number, number] => {
18 | const radians = angle / 180 * Math.PI;
19 | return [
20 | Math.cos(radians) * distance,
21 | Math.sin(radians) * distance,
22 | ];
23 | };
24 |
25 | export const getAngleFromPosition = (x: number, y: number): number => {
26 | return Math.atan2(y, x) * 180 / Math.PI;
27 | };
28 |
29 | export const keepCircleInsideCircle = (
30 | parentR: number,
31 | parentPosition: [number, number],
32 | childR: number,
33 | childPosition: [number, number],
34 | isParent: boolean = false,
35 | ): [number, number] => {
36 | const distance = Math.sqrt(
37 | Math.pow(parentPosition[0] - childPosition[0], 2) +
38 | Math.pow(parentPosition[1] - childPosition[1], 2),
39 | );
40 | const angle = getAngleFromPosition(
41 | childPosition[0] - parentPosition[0],
42 | childPosition[1] - parentPosition[1],
43 | );
44 | // leave space for labels
45 | const padding = Math.min(
46 | angle < -20 && angle > -100 && isParent ? 13 : 3,
47 | parentR * 0.2,
48 | );
49 | if (distance > (parentR - childR - padding)) {
50 | const diff = getPositionFromAngleAndDistance(
51 | angle,
52 | parentR - childR - padding,
53 | );
54 | return [
55 | parentPosition[0] + diff[0],
56 | parentPosition[1] + diff[1],
57 | ];
58 | }
59 | return childPosition;
60 | };
61 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 |
6 | "jsx": "react",
7 | "resolveJsonModule": true,
8 | "rootDirs": [
9 | "src"
10 | ],
11 | "allowSyntheticDefaultImports": true,
12 | "forceConsistentCasingInFileNames": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------