git-truck 0.8.2 → 0.8.6-experimental

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.github/workflows/bump-version.yml +1 -1
  2. package/README.md +17 -13
  3. package/cli.js +2 -0
  4. package/dev.js +4 -2
  5. package/package.json +4 -4
  6. package/server.ts +13 -8
  7. package/src/analyzer/analyze.server.ts +43 -76
  8. package/src/analyzer/analyze.test.ts +30 -30
  9. package/src/analyzer/args.server.ts +20 -6
  10. package/src/analyzer/constants.ts +1 -1
  11. package/src/analyzer/git-caller.server.ts +290 -0
  12. package/src/analyzer/hydrate.server.ts +1 -1
  13. package/src/analyzer/model.ts +13 -2
  14. package/src/analyzer/{util.ts → util.server.ts} +27 -33
  15. package/src/analyzer/util.test.ts +1 -1
  16. package/src/components/AnalyzingIndicator.tsx +55 -0
  17. package/src/components/Animations.ts +14 -0
  18. package/src/components/Chart.tsx +29 -8
  19. package/src/components/Details.tsx +8 -7
  20. package/src/components/GlobalInfo.tsx +19 -8
  21. package/src/components/HiddenFiles.tsx +3 -3
  22. package/src/components/Legend.tsx +1 -1
  23. package/src/components/LegendOther.tsx +42 -42
  24. package/src/components/Main.tsx +1 -1
  25. package/src/components/SearchBar.tsx +1 -6
  26. package/src/components/util.tsx +19 -10
  27. package/src/const.ts +6 -6
  28. package/src/contexts/ClickedContext.ts +17 -17
  29. package/src/contexts/DataContext.ts +12 -12
  30. package/src/contexts/MetricContext.ts +12 -12
  31. package/src/contexts/OptionsContext.ts +51 -51
  32. package/src/contexts/SearchContext.ts +19 -19
  33. package/src/lang-map.d.ts +3 -3
  34. package/src/metrics.ts +3 -2
  35. package/src/root.tsx +44 -1
  36. package/src/routes/{repo.tsx → $repo.tsx} +59 -15
  37. package/src/routes/index.tsx +156 -46
  38. package/build/index.js +0 -6836
  39. package/post-build.js +0 -14
  40. package/public/favicon.ico +0 -0
  41. package/src/analyzer/git-caller.ts +0 -117
  42. package/src/analyzer/index.ts +0 -4
@@ -1,30 +1,49 @@
1
- import { ActionFunction, json, LoaderFunction, useLoaderData } from "remix"
1
+ import { ActionFunction, ErrorBoundaryComponent, json, Link, LoaderFunction, useLoaderData } from "remix"
2
2
  import { Providers } from "~/components/Providers"
3
- import { Box, Container, Grower, InlineCode, StyledP } from "~/components/util"
3
+ import { Box, Container, Grower, Code, StyledP } from "~/components/util"
4
4
  import { SidePanel } from "~/components/SidePanel"
5
5
  import { Main } from "~/components/Main"
6
- import { AnalyzerData } from "~/analyzer/model"
6
+ import { AnalyzerData, TruckUserConfig } from "~/analyzer/model"
7
7
  import { analyze, openFile, updateTruckConfig } from "~/analyzer/analyze.server"
8
8
  import { GlobalInfo } from "~/components/GlobalInfo"
9
9
  import { Options } from "~/components/Options"
10
10
  import SearchBar from "~/components/SearchBar"
11
11
  import { Spacer } from "~/components/Spacer"
12
12
  import { Legend } from "~/components/Legend"
13
- import { getArgs } from "~/analyzer/args.server"
13
+ import { getTruckConfigWithArgs } from "~/analyzer/args.server"
14
14
  import { HiddenFiles } from "~/components/HiddenFiles"
15
15
  import semverCompare from "semver-compare"
16
16
  import { Details } from "~/components/Details"
17
+ import { resolve } from "path"
17
18
 
18
- let useCacheNextTime = false
19
+ let invalidateCache = false
19
20
 
20
- export const loader: LoaderFunction = async () => {
21
- const useCache = useCacheNextTime
22
- const data = await analyze(useCache)
23
- useCacheNextTime = false
21
+ export const loader: LoaderFunction = async ({ params, request }) => {
22
+ if (params["repo"] === "favicon.ico") {
23
+ return null
24
+ }
25
+
26
+ const args = await getTruckConfigWithArgs(params["repo"] as string)
27
+
28
+ const options: TruckUserConfig = {
29
+ invalidateCache: invalidateCache || args.invalidateCache,
30
+ }
31
+ if (params["repo"]) {
32
+ options.path = resolve(args.path, params["repo"])
33
+ }
34
+
35
+ const branch = new URL(request.url).searchParams.get("branch")
36
+ if (branch) options.branch = decodeURIComponent(branch)
37
+
38
+ const data = await analyze({ ...args, ...options })
39
+ invalidateCache = false
24
40
  return json<AnalyzerData>(data)
25
41
  }
26
42
 
27
- export const action: ActionFunction = async ({ request }) => {
43
+ export const action: ActionFunction = async ({ request, params }) => {
44
+ if (!params["repo"]) {
45
+ throw Error("This can never happen, since this route is only called if a repo exists in the URL")
46
+ }
28
47
  const formData = await request.formData()
29
48
  const refresh = formData.get("refresh")
30
49
  const unignore = formData.get("unignore")
@@ -32,12 +51,15 @@ export const action: ActionFunction = async ({ request }) => {
32
51
  const fileToOpen = formData.get("open")
33
52
 
34
53
  if (refresh) {
35
- useCacheNextTime = true
54
+ invalidateCache = true
36
55
  return null
37
56
  }
38
57
 
58
+ const args = await getTruckConfigWithArgs(params["repo"])
59
+ const path = resolve(args.path, params["repo"])
60
+
39
61
  if (ignore && typeof ignore === "string") {
40
- await updateTruckConfig((await getArgs()).path, (prevConfig) => {
62
+ await updateTruckConfig(path, (prevConfig) => {
41
63
  const hiddenFilesSet = new Set((prevConfig?.hiddenFiles ?? []).map((x) => x.trim()))
42
64
  hiddenFilesSet.add(ignore)
43
65
 
@@ -50,7 +72,7 @@ export const action: ActionFunction = async ({ request }) => {
50
72
  }
51
73
 
52
74
  if (unignore && typeof unignore === "string") {
53
- await updateTruckConfig((await getArgs()).path, (prevConfig) => {
75
+ await updateTruckConfig(resolve(args.path, params["repo"]), (prevConfig) => {
54
76
  const hiddenFilesSet = new Set((prevConfig?.hiddenFiles ?? []).map((x) => x.trim()))
55
77
  hiddenFilesSet.delete(unignore.trim())
56
78
 
@@ -70,9 +92,32 @@ export const action: ActionFunction = async ({ request }) => {
70
92
  return null
71
93
  }
72
94
 
95
+ export const ErrorBoundary: ErrorBoundaryComponent = ({ error }) => {
96
+ console.error(error.message)
97
+ console.error(error.stack)
98
+ return (
99
+ <Container>
100
+ <div />
101
+ <Box>
102
+ <h1>An error occured!</h1>
103
+ <p>See console for more infomation.</p>
104
+ <Code>{error.stack}</Code>
105
+ <div>
106
+ <Link to=".">Retry</Link>
107
+ </div>
108
+ <div>
109
+ <Link to="..">Go back</Link>
110
+ </div>
111
+ </Box>
112
+ </Container>
113
+ )
114
+ }
115
+
73
116
  export default function Index() {
74
117
  const data = useLoaderData<AnalyzerData>()
75
118
 
119
+ if (!data) return null
120
+
76
121
  return (
77
122
  <Providers data={data}>
78
123
  <Container>
@@ -89,8 +134,7 @@ export default function Index() {
89
134
  <p>Update available: {data.latestVersion}</p>
90
135
  <StyledP>Currently installed: {data.currentVersion}</StyledP>
91
136
  <StyledP>
92
- To update, close application and run:{" "}
93
- <InlineCode>npx git-truck@latest</InlineCode>
137
+ To update, close application and run: <Code inline>npx git-truck@latest</Code>
94
138
  </StyledP>
95
139
  </Box>
96
140
  ) : null}
@@ -1,62 +1,172 @@
1
- import { useNavigate } from "react-router-dom"
2
- import { useEffect } from "react"
1
+ import { ActionFunction, json, Link, LoaderFunction, useLoaderData, useNavigate, useSubmit, useTransition } from "remix"
3
2
  import styled from "styled-components"
3
+ import { getArgsWithDefaults } from "~/analyzer/args.server"
4
+ import { getBaseDirFromPath, getDirName } from "~/analyzer/util.server"
5
+ import { Spacer } from "~/components/Spacer"
6
+ import { Box, BoxSubTitle, Code, Grower, TextButton } from "~/components/util"
7
+ import { AnalyzingIndicator } from "~/components/AnalyzingIndicator"
8
+ import { resolve } from "path"
9
+ import { Repository } from "~/analyzer/model"
10
+ import { GitCaller } from "~/analyzer/git-caller.server"
11
+ import { useMount } from "react-use"
4
12
 
5
- const LoadingPane = styled.div`
6
- padding: 0.5em 2em;
7
- display: grid;
8
- place-items: center;
9
- border-radius: 5px;
13
+ interface IndexData {
14
+ repositories: Repository[]
15
+ baseDir: string
16
+ baseDirName: string
17
+ repo: Repository | null
18
+ hasRedirected: boolean
19
+ }
10
20
 
11
- /* hide_initially animation */
12
- opacity: 0;
13
- animation: hide_initially 0s linear forwards;
14
- animation-delay: 1s;
15
- `
21
+ let hasRedirected = false
16
22
 
17
- const FullViewbox = styled.div`
18
- display: grid;
19
- place-items: center;
20
- height: 100vh;
21
- width: 100vw;
22
- `
23
+ export const loader: LoaderFunction = async () => {
24
+ const args = await getArgsWithDefaults()
25
+ const [repo, repositories] = await GitCaller.scanDirectoryForRepositories(args.path)
26
+ const baseDir = resolve(repo ? getBaseDirFromPath(args.path) : args.path)
27
+ const repositoriesResponse = json<IndexData>({
28
+ repositories,
29
+ baseDir,
30
+ baseDirName: getDirName(baseDir),
31
+ repo,
32
+ hasRedirected,
33
+ })
23
34
 
24
- const StyledPath = styled.path`
25
- fill: none;
26
- stroke: #4580ff;
27
- /* transition: 1s; */
28
- animation: dash 2s ease-in-out alternate infinite;
29
- `
35
+ const response = repositoriesResponse
36
+ hasRedirected = true
30
37
 
31
- const LoadingText = styled.div`
32
- grid-area: 1/2;
33
- `
38
+ return response
39
+ }
34
40
 
35
- const StyledSVG = styled.svg`
36
- grid-area: 1/2;
37
- `
41
+ export const action: ActionFunction = async ({ request }) => {
42
+ const formData = await request.formData()
43
+ if (formData.has("hasRedirected")) {
44
+ hasRedirected = true
45
+ }
46
+ return null
47
+ }
38
48
 
39
49
  export default function Index() {
50
+ const loadterData = useLoaderData<IndexData>()
51
+ const { repositories, baseDir, baseDirName, repo, hasRedirected } = loadterData
52
+ const transitionData = useTransition()
40
53
  const navigate = useNavigate()
41
- useEffect(() => {
42
- navigate("/repo/")
43
- }, [navigate])
54
+ const submit = useSubmit()
44
55
 
45
- const width = 20
46
- const height = 20
47
- const length = width + height
56
+ const willRedirect = repo && !hasRedirected
57
+ useMount(() => {
58
+ if (willRedirect) {
59
+ const data = new FormData()
60
+ data.append("hasRedirected", "true")
61
+ submit(data, { method: "post" })
62
+ navigate(`/${repo.name}`)
63
+ }
64
+ })
48
65
 
49
- const path = `M0,0 m-${width * 0.5},-${height * 0.5} l${width},0 l0,${height} l-${width},0 l0,-${height} Z`
50
- const viewBox = `-${height * 0.5} -${width * 0.5} ${height} ${width}`
66
+ const cachedRepositories = repositories.filter((repo) => repo.data?.cached)
67
+ const notCachedRepositories = repositories.filter((repo) => !repo.data?.cached)
51
68
 
69
+ if (transitionData.state !== "idle" || willRedirect) return <AnalyzingIndicator />
52
70
  return (
53
- <FullViewbox>
54
- <LoadingPane>
55
- <LoadingText>Analyzing...</LoadingText>
56
- <StyledSVG height="160px" width="160px" viewBox={viewBox}>
57
- <StyledPath strokeDasharray={length * 0.5} strokeDashoffset={length * 2} d={path}></StyledPath>
58
- </StyledSVG>
59
- </LoadingPane>
60
- </FullViewbox>
71
+ <Wrapper>
72
+ <Spacer />
73
+ <H1>{baseDir}</H1>
74
+ <Spacer />
75
+ <p>
76
+ Found {repositories.length} git repositories in the folder <Code inline>{baseDirName}</Code>.
77
+ </p>
78
+ {repositories.length === 0 ? (
79
+ <>
80
+ <Spacer />
81
+ <p>
82
+ Try running <Code inline>git-truck</Code> in another folder or provide another path as argument.
83
+ </p>
84
+ </>
85
+ ) : null}
86
+ {cachedRepositories.length > 0 ? (
87
+ <>
88
+ <Spacer xxl />
89
+ <h2>Ready to view</h2>
90
+ <nav>
91
+ <Ul>
92
+ {cachedRepositories.map((repo) => (
93
+ <Li key={repo.name}>
94
+ <SLink to={repo.name} tabIndex={-1}>
95
+ <Box>
96
+ <BoxSubTitle title={repo.name}>{repo.name}</BoxSubTitle>
97
+ <Spacer />
98
+ <Actions>
99
+ <Grower />
100
+ <TextButton>{repo.data?.cached ? "View" : "Analyze"}</TextButton>
101
+ </Actions>
102
+ </Box>
103
+ </SLink>
104
+ </Li>
105
+ ))}
106
+ </Ul>
107
+ </nav>
108
+ </>
109
+ ) : null}
110
+ {notCachedRepositories.length > 0 ? (
111
+ <>
112
+ <Spacer xxl />
113
+ <h2>Needs to be analyzed before viewing</h2>
114
+ <nav>
115
+ <Ul>
116
+ {notCachedRepositories.map((repo) => (
117
+ <Li key={repo.name}>
118
+ <SLink to={repo.name} tabIndex={-1}>
119
+ <Box>
120
+ <BoxSubTitle title={repo.name}>{repo.name}</BoxSubTitle>
121
+ <Spacer />
122
+ <Actions>
123
+ <Grower />
124
+ <TextButton>{repo.data?.cached ? "View" : "Analyze"}</TextButton>
125
+ </Actions>
126
+ </Box>
127
+ </SLink>
128
+ </Li>
129
+ ))}
130
+ </Ul>
131
+ </nav>
132
+ </>
133
+ ) : null}
134
+ </Wrapper>
61
135
  )
62
136
  }
137
+
138
+ const Wrapper = styled.div`
139
+ width: calc(100vw - 2 * var(--side-panel-width));
140
+ margin: auto;
141
+ `
142
+ const H1 = styled.h1`
143
+ font-family: "Courier New", Courier, monospace;
144
+ `
145
+
146
+ const Ul = styled.ul`
147
+ list-style: none;
148
+ display: grid;
149
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
150
+ `
151
+
152
+ const Li = styled.li`
153
+ margin: 0;
154
+ `
155
+
156
+ const Tag = styled.span`
157
+ font-size: 0.7em;
158
+ text-transform: uppercase;
159
+ padding: calc(0.25 * var(--unit)) calc(var(--unit));
160
+ letter-spacing: 0.2em;
161
+ color: white;
162
+ background-color: hsl(210, 100%, 50%);
163
+ border-radius: 1000px;
164
+ `
165
+
166
+ const Actions = styled.div`
167
+ display: flex;
168
+ `
169
+
170
+ const SLink = styled(Link)`
171
+ text-decoration: none;
172
+ `