Skip to content

Commit 74252a7

Browse files
committed
add command to dump expanded route config as JSON
1 parent 661637b commit 74252a7

8 files changed

Lines changed: 339 additions & 17 deletions

.changeset/nervous-rivers-taste.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"rescript-relay-router": minor
3+
---
4+
5+
Add command to dump expanded routes as JSON.

packages/rescript-relay-router/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,56 @@ switch Routes.Organization.Members.Route.parseRoute(link){
609609
let activeSubRoute = Routes.Organization.Route.useActiveSubRoute()
610610
```
611611

612+
## CLI utilities
613+
614+
### Dumping routes
615+
616+
You can dump all route URLs from your configured route tree:
617+
618+
```bash
619+
yarn rescript-relay-router dump-routes
620+
```
621+
622+
This prints a JSON array in route definition order:
623+
624+
```json
625+
[
626+
{ "url": "/organization/:slug" },
627+
{ "url": "/organization/:slug/members" },
628+
{ "url": "/" }
629+
]
630+
```
631+
632+
To sort alphabetically by `url`, pass `--sort alphabetic`.
633+
634+
Use flags to include query params and route metadata:
635+
636+
```bash
637+
yarn rescript-relay-router dump-routes \
638+
--sort alphabetic \
639+
--include-query-params \
640+
--include-name \
641+
--include-route-renderer-path \
642+
--include-route-file-path
643+
```
644+
645+
With metadata enabled, `url` is still emitted first:
646+
647+
```json
648+
[
649+
{
650+
"url": "/organization/:slug/members",
651+
"queryParams": {
652+
"after": ":after",
653+
"first": ":first"
654+
},
655+
"name": "Organization__Members",
656+
"routeRendererPath": "src/routes/Organization__Members_route_renderer.res",
657+
"routeFilePath": "src/routes/routes.json"
658+
}
659+
]
660+
```
661+
612662
## FAQ
613663

614664
- Check in or don't check in generated assets?

packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.res

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ module Path = {
3636
@module("path")
3737
external dirname: string => string = "dirname"
3838

39+
@module("path")
40+
external relative: (string, string) => string = "relative"
41+
3942
@module("path")
4043
external basename: string => string = "basename"
4144

packages/rescript-relay-router/cli/RescriptRelayRouterCli__Bindings.resi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ module Path: {
3434
@module("path")
3535
external dirname: string => string = "dirname"
3636

37+
@module("path")
38+
external relative: (string, string) => string = "relative"
39+
3740
@module("path")
3841
external basename: string => string = "basename"
3942

packages/rescript-relay-router/cli/RescriptRelayRouterCli__Commands.res

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Codegen = RescriptRelayRouterCli__Codegen
22
module Utils = RescriptRelayRouterCli__Utils
33
module Types = RescriptRelayRouterCli__Types
44
module Diagnostics = RescriptRelayRouterCli__Diagnostics
5+
module DumpRoutes = RescriptRelayRouterCli__DumpRoutes
56

67
open RescriptRelayRouterCli__Bindings
78

@@ -239,8 +240,16 @@ let printRouteInfo = (~url, ~config) => {
239240
}
240241
}
241242

242-
@val
243-
external stringifyFormatted: ('any, @as(json`null`) _, @as(json`2`) _) => string = "JSON.stringify"
243+
let rec dumpRoutesSortOrder = options => {
244+
switch options {
245+
| list{"--sort", "alphabetic", ..._}
246+
| list{"--sort=alphabetic", ..._} => Types.Alphabetic
247+
| list{"--sort", "definition", ..._}
248+
| list{"--sort=definition", ..._} => Types.DefinitionOrder
249+
| list{_, ...rest} => dumpRoutesSortOrder(rest)
250+
| list{} => Types.DefinitionOrder
251+
}
252+
}
244253

245254
let init = () => {
246255
if !Utils.Config.exists() {
@@ -249,9 +258,9 @@ let init = () => {
249258

250259
Fs.writeFileSync(
251260
path,
252-
`module.exports = ${{
253-
"routesFolderPath": "./src/routes",
254-
}->stringifyFormatted}`,
261+
`module.exports = ${JSON.Object(dict{
262+
"routesFolderPath": JSON.String("./src/routes"),
263+
})->JSON.stringify(~space=2)}`,
255264
)
256265

257266
Console.log("[init] Config created at: " ++ path)
@@ -267,17 +276,17 @@ let init = () => {
267276
Console.log("[init] `routes.json` does not exist, creating...")
268277
Fs.writeFileSync(
269278
routesJsonPath,
270-
(
271-
{
272-
"path": "/",
273-
"name": "Root",
274-
"children": [],
275-
},
276-
{
277-
"path": "*",
278-
"name": "FourOhFour",
279-
},
280-
)->stringifyFormatted,
279+
JSON.Array([
280+
JSON.Object(dict{
281+
"path": JSON.String("/"),
282+
"name": JSON.String("Root"),
283+
"children": JSON.Array([]),
284+
}),
285+
JSON.Object(dict{
286+
"path": JSON.String("*"),
287+
"name": JSON.String("FourOhFour"),
288+
}),
289+
])->JSON.stringify(~space=2),
281290
)
282291

283292
Console.log("[init] Basic `routes.json` added at: " ++ routesJsonPath)
@@ -317,7 +326,14 @@ let runCli = args => {
317326
| have a route defined anymore.
318327
319328
find-route <url> | Shows what routes/components will render for a specific route.
320-
| Example: find-route /todos/123`)
329+
| Example: find-route /todos/123
330+
331+
dump-routes
332+
[--sort definition|alphabetic]
333+
[--include-query-params]
334+
[--include-name]
335+
[--include-route-renderer-path]
336+
[--include-route-file-path] | Dumps all routes as JSON. Sorts by definition order by default.`)
321337
Done
322338
| list{"scaffold-route-renderers", ...options} =>
323339
let deleteRemoved = options->List.has("-delete-removed", String.equal)
@@ -384,6 +400,19 @@ let runCli = args => {
384400
let config = Utils.Config.load()
385401
printRouteInfo(~url=route, ~config)
386402
Done
403+
| list{"dump-routes", ...options} =>
404+
let config = Utils.Config.load()
405+
DumpRoutes.run(
406+
~config,
407+
~options={
408+
includeQueryParams: options->List.has("--include-query-params", String.equal),
409+
includeName: options->List.has("--include-name", String.equal),
410+
includeRouteRendererPath: options->List.has("--include-route-renderer-path", String.equal),
411+
includeRouteFilePath: options->List.has("--include-route-file-path", String.equal),
412+
sortOrder: dumpRoutesSortOrder(options),
413+
},
414+
)
415+
Done
387416
| list{"init"} =>
388417
init()
389418
Done
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
open RescriptRelayRouterCli__Types
2+
open RescriptRelayRouterCli__Bindings
3+
4+
module Utils = RescriptRelayRouterCli__Utils
5+
6+
let pathRelativeToCwd = path => Path.relative(Process.cwd(), path)
7+
8+
let routeUrl = (route: printableRoute) => route.path->RoutePath.getFullRoutePath
9+
10+
let queryParamsObject = (route: printableRoute) => {
11+
let queryParams = Dict.make()
12+
let queryParamKeys =
13+
route.queryParams
14+
->Dict.toArray
15+
->Array.map(((key, _)) => key)
16+
queryParamKeys->Array.sort(String.localeCompare)
17+
18+
queryParamKeys->Array.forEach(key => {
19+
queryParams->Dict.set(key, JSON.String(`:${key}`))
20+
})
21+
22+
queryParams
23+
}
24+
25+
let routeRendererPath = (~config, route: printableRoute) =>
26+
Utils.pathInRoutesFolder(~config, ~fileName=route.name->RouteName.getRouteRendererFileName)
27+
->pathRelativeToCwd
28+
29+
let routeFilePath = (~config, route: printableRoute) =>
30+
Utils.pathInRoutesFolder(~config, ~fileName=route.sourceFile)->pathRelativeToCwd
31+
32+
let rec flattenRoutes = (routes: array<printableRoute>): array<printableRoute> => {
33+
let allRoutes = []
34+
35+
routes->Array.forEach(route => {
36+
allRoutes->Array.push(route)
37+
route.children->flattenRoutes->Array.forEach(route => allRoutes->Array.push(route))
38+
})
39+
40+
allRoutes
41+
}
42+
43+
let urlFromDumpedRoute = route => {
44+
switch route->Dict.get("url") {
45+
| Some(JSON.String(url)) => url
46+
| _ => ""
47+
}
48+
}
49+
50+
let sortRoutes = (routes, ~sortOrder) => {
51+
switch sortOrder {
52+
| DefinitionOrder => ()
53+
| Alphabetic =>
54+
routes->Array.sort((a, b) =>
55+
String.localeCompare(a->urlFromDumpedRoute, b->urlFromDumpedRoute)
56+
)
57+
}
58+
}
59+
60+
let dump = (~routes, ~config, ~options: dumpRoutesOptions) => {
61+
let routes =
62+
routes
63+
->flattenRoutes
64+
->Array.map(route => {
65+
let item = Dict.make()
66+
item->Dict.set("url", route->routeUrl->JSON.String)
67+
68+
if options.includeQueryParams {
69+
item->Dict.set("queryParams", route->queryParamsObject->JSON.Object)
70+
}
71+
72+
if options.includeName {
73+
item->Dict.set("name", route.name->RouteName.getFullRouteName->JSON.String)
74+
}
75+
76+
if options.includeRouteRendererPath {
77+
item->Dict.set("routeRendererPath", route->routeRendererPath(~config)->JSON.String)
78+
}
79+
80+
if options.includeRouteFilePath {
81+
item->Dict.set("routeFilePath", route->routeFilePath(~config)->JSON.String)
82+
}
83+
84+
item
85+
})
86+
87+
routes->sortRoutes(~sortOrder=options.sortOrder)
88+
routes
89+
}
90+
91+
let run = (~options, ~config) => {
92+
let (routes, _routeNamesDict) = Utils.readRouteStructure(config)
93+
Console.log(
94+
dump(~routes, ~config, ~options)
95+
->Array.map(route => JSON.Object(route))
96+
->JSON.Array
97+
->JSON.stringify(~space=2),
98+
)
99+
}

packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,16 @@ type config = {
244244
rescriptLibFolderPath: string,
245245
}
246246

247+
type dumpRoutesSortOrder = DefinitionOrder | Alphabetic
248+
249+
type dumpRoutesOptions = {
250+
includeQueryParams: bool,
251+
includeName: bool,
252+
includeRouteRendererPath: bool,
253+
includeRouteFilePath: bool,
254+
sortOrder: dumpRoutesSortOrder,
255+
}
256+
247257
type dependencyDeclaration = {
248258
dependsOn: Set.t<string>,
249259
dependents: Set.t<string>,

0 commit comments

Comments
 (0)