@uxf/router 11.72.0 → 11.72.2
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.
- package/package.json +1 -1
- package/router.d.ts +3 -7
- package/router.js +3 -1
- package/routes-check.d.ts +5 -1
- package/routes-check.js +8 -4
- package/routes-check.test.js +21 -21
- package/sitemap-generator.d.ts +21 -11
- package/sitemap-generator.js +22 -27
- package/sitemap-generator.test.js +15 -9
- package/types.d.ts +5 -5
package/package.json
CHANGED
package/router.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LinkProps } from "next/link";
|
|
2
2
|
import { Infer, Struct } from "superstruct";
|
|
3
|
-
import { SitemapGeneratorOptions, SitemapGeneratorType } from "./sitemap-generator";
|
|
3
|
+
import { SitemapGeneratorOptions, SitemapGeneratorType, SitemapRouteResolvers } from "./sitemap-generator";
|
|
4
4
|
import { QueryParams, RoutesDefinition } from "./types";
|
|
5
5
|
type ExtractSchema<T> = T extends {
|
|
6
6
|
schema: Struct<infer U, any>;
|
|
@@ -12,11 +12,7 @@ type LocaleOptions<Locales extends string[]> = {
|
|
|
12
12
|
locale: Locales[number];
|
|
13
13
|
};
|
|
14
14
|
export type FunctionParametersGenerator<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
|
|
15
|
-
[K in keyof RouteList]: RouteList[K]["path"] extends string ? RouteList[K] extends
|
|
16
|
-
schema: undefined;
|
|
17
|
-
} ? [K, null?, Options?] : [K, ExtractSchema<RouteList[K]>?, Options?] : RouteList[K] extends {
|
|
18
|
-
schema: undefined;
|
|
19
|
-
} ? [K, null, Options & LocaleOptions<Locales>] : [K, ExtractSchema<RouteList[K]>, Options & LocaleOptions<Locales>];
|
|
15
|
+
[K in keyof RouteList]: RouteList[K]["path"] extends string ? "schema" extends keyof RouteList[K] ? [K, ExtractSchema<RouteList[K]>, Options?] : [K, null?, Options?] : "schema" extends keyof RouteList[K] ? [K, ExtractSchema<RouteList[K]>, Options & LocaleOptions<Locales>] : [K, null, Options & LocaleOptions<Locales>];
|
|
20
16
|
}[keyof RouteList];
|
|
21
17
|
type RouteFunction<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = (...args: FunctionParametersGenerator<Locales, RouteList>) => LinkProps["href"];
|
|
22
18
|
type RouteToUrlFunction<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = (...args: FunctionParametersGenerator<Locales, RouteList>) => string;
|
|
@@ -30,7 +26,7 @@ type QueryParamsResult<Nullable extends boolean, T extends keyof RouteList, Loca
|
|
|
30
26
|
type Router<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
|
|
31
27
|
route: RouteFunction<Locales, RouteList>;
|
|
32
28
|
routeToUrl: RouteToUrlFunction<Locales, RouteList>;
|
|
33
|
-
createSitemapGenerator: (options?: SitemapGeneratorOptions) => SitemapGeneratorType
|
|
29
|
+
createSitemapGenerator: (resolvers: SitemapRouteResolvers<Locales, RouteList>, options?: SitemapGeneratorOptions) => SitemapGeneratorType;
|
|
34
30
|
routes: RouteList;
|
|
35
31
|
useActiveRoute: () => ActiveRoute<Locales, RouteList>;
|
|
36
32
|
/**
|
package/router.js
CHANGED
|
@@ -97,7 +97,9 @@ function createRouter(routes, routerOptions) {
|
|
|
97
97
|
locale: ((_a = router.locale) !== null && _a !== void 0 ? _a : null),
|
|
98
98
|
};
|
|
99
99
|
},
|
|
100
|
-
createSitemapGenerator
|
|
100
|
+
createSitemapGenerator(resolvers, options) {
|
|
101
|
+
return new sitemap_generator_1.SitemapGenerator(resolvers, options);
|
|
102
|
+
},
|
|
101
103
|
routes,
|
|
102
104
|
useQueryParamsDeprecated: () => (0, router_1.useRouter)().query,
|
|
103
105
|
useQueryParams(routeName) {
|
package/routes-check.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node-script
|
|
2
|
-
|
|
2
|
+
import { RoutesDefinition } from "./types";
|
|
3
|
+
export declare function routesCheck(routes: RoutesDefinition<any>, options?: {
|
|
4
|
+
shouldProcessExit?: boolean;
|
|
5
|
+
defaultLocale?: string;
|
|
6
|
+
}): void;
|
package/routes-check.js
CHANGED
|
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.routesCheck = routesCheck;
|
|
8
8
|
const is_not_empty_1 = require("@uxf/core/utils/is-not-empty");
|
|
9
|
+
const throw_error_1 = require("@uxf/core/utils/throw-error");
|
|
9
10
|
const fs_1 = __importDefault(require("fs"));
|
|
10
11
|
const path_1 = require("path");
|
|
11
12
|
const true_case_path_1 = require("true-case-path");
|
|
@@ -58,9 +59,12 @@ function checkFolderFileCollision(baseDir, pathSegments) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
|
-
function routesCheck(routes,
|
|
62
|
+
function routesCheck(routes, options = {}) {
|
|
62
63
|
Object.keys(routes).forEach((route) => {
|
|
63
|
-
|
|
64
|
+
var _a;
|
|
65
|
+
const pathname = typeof routes[route].path === "string"
|
|
66
|
+
? routes[route].path
|
|
67
|
+
: routes[route].path[(_a = options.defaultLocale) !== null && _a !== void 0 ? _a : (0, throw_error_1.throwError)("Default locale must be provided")];
|
|
64
68
|
try {
|
|
65
69
|
const basePath = (0, path_1.join)(__dirname, "..", "..", "src", "pages");
|
|
66
70
|
const appBasePath = (0, path_1.join)(__dirname, "..", "..", "src", "app");
|
|
@@ -102,13 +106,13 @@ function routesCheck(routes, shouldProcessExit = true) {
|
|
|
102
106
|
}
|
|
103
107
|
});
|
|
104
108
|
if (error) {
|
|
105
|
-
if (shouldProcessExit) {
|
|
109
|
+
if (options.shouldProcessExit) {
|
|
106
110
|
process.exit(1);
|
|
107
111
|
}
|
|
108
112
|
throw new Error("At least one invalid route found");
|
|
109
113
|
}
|
|
110
114
|
else {
|
|
111
|
-
if (shouldProcessExit) {
|
|
115
|
+
if (options.shouldProcessExit) {
|
|
112
116
|
process.exit(0);
|
|
113
117
|
}
|
|
114
118
|
// eslint-disable-next-line no-console
|
package/routes-check.test.js
CHANGED
|
@@ -75,14 +75,13 @@ describe("routesCheck", () => {
|
|
|
75
75
|
(0, path_1.join)(rootDirApp, "settings"),
|
|
76
76
|
(0, path_1.join)(rootDirApp, "settings", "privacy"),
|
|
77
77
|
]);
|
|
78
|
-
|
|
79
|
-
product: "/product",
|
|
80
|
-
productEdit: "/product/edit",
|
|
81
|
-
productDetail: "/product/[id]",
|
|
82
|
-
dashboard: "/dashboard",
|
|
83
|
-
privacy: "/settings/privacy",
|
|
84
|
-
};
|
|
85
|
-
expect(() => (0, routes_check_1.routesCheck)(routes, false)).not.toThrow();
|
|
78
|
+
expect(() => (0, routes_check_1.routesCheck)({
|
|
79
|
+
product: { path: "/product" },
|
|
80
|
+
productEdit: { path: "/product/edit" },
|
|
81
|
+
productDetail: { path: "/product/[id]" },
|
|
82
|
+
dashboard: { path: "/dashboard" },
|
|
83
|
+
privacy: { path: "/settings/privacy" },
|
|
84
|
+
}, { shouldProcessExit: false })).not.toThrow();
|
|
86
85
|
});
|
|
87
86
|
it("should fail when invalid routes are provided", () => {
|
|
88
87
|
/**
|
|
@@ -92,11 +91,10 @@ describe("routesCheck", () => {
|
|
|
92
91
|
*/
|
|
93
92
|
// Intentionally not mocking any matching paths here
|
|
94
93
|
mockFileExists([]);
|
|
95
|
-
|
|
96
|
-
unknown: "/unknown",
|
|
97
|
-
productUnknown: "/product/unknown",
|
|
98
|
-
};
|
|
99
|
-
expect(() => (0, routes_check_1.routesCheck)(routes, false)).toThrow();
|
|
94
|
+
expect(() => (0, routes_check_1.routesCheck)({
|
|
95
|
+
unknown: { path: "/unknown" },
|
|
96
|
+
productUnknown: { path: "/product/unknown" },
|
|
97
|
+
}, { shouldProcessExit: false })).toThrow();
|
|
100
98
|
});
|
|
101
99
|
it("Pages folder: should detect collisions (file + folder) in multiple segments", () => {
|
|
102
100
|
/**
|
|
@@ -108,10 +106,9 @@ describe("routesCheck", () => {
|
|
|
108
106
|
(0, path_1.join)(rootDirPages, "payments.tsx"),
|
|
109
107
|
(0, path_1.join)(rootDirPages, "payments", "export.tsx"),
|
|
110
108
|
], [(0, path_1.join)(rootDirPages, "payments")]);
|
|
111
|
-
|
|
112
|
-
paymentsExport: "/payments/export",
|
|
113
|
-
};
|
|
114
|
-
expect(() => (0, routes_check_1.routesCheck)(routes, false)).toThrow();
|
|
109
|
+
expect(() => (0, routes_check_1.routesCheck)({
|
|
110
|
+
paymentsExport: { path: "/payments/export" },
|
|
111
|
+
}, { shouldProcessExit: false })).toThrow();
|
|
115
112
|
});
|
|
116
113
|
it("App folder: should detect that page.tsx is not created for the route", () => {
|
|
117
114
|
/**
|
|
@@ -122,9 +119,12 @@ describe("routesCheck", () => {
|
|
|
122
119
|
// app collisions
|
|
123
120
|
(0, path_1.join)(rootDirApp, "admin", "analytics.tsx"),
|
|
124
121
|
], [(0, path_1.join)(rootDirApp, "admin"), (0, path_1.join)(rootDirApp, "admin", "analytics")]);
|
|
125
|
-
|
|
126
|
-
adminAnalytics:
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
expect(() => (0, routes_check_1.routesCheck)({
|
|
123
|
+
adminAnalytics: {
|
|
124
|
+
path: "/admin/analytics",
|
|
125
|
+
},
|
|
126
|
+
}, {
|
|
127
|
+
shouldProcessExit: false,
|
|
128
|
+
})).toThrow();
|
|
129
129
|
});
|
|
130
130
|
});
|
package/sitemap-generator.d.ts
CHANGED
|
@@ -7,22 +7,32 @@ export type SitemapItem = {
|
|
|
7
7
|
changefreq?: ChangeFrequency;
|
|
8
8
|
};
|
|
9
9
|
type RouteResolverResult = null | undefined | SitemapItem | Array<SitemapItem | null | undefined>;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Object-based approach for sitemap generation with compile-time exhaustiveness checking.
|
|
12
|
+
*
|
|
13
|
+
* Instead of builder pattern, this requires all routes to be specified in a single object.
|
|
14
|
+
* Routes that shouldn't be in sitemap are set to null, others have RouteResolver functions.
|
|
15
|
+
*
|
|
16
|
+
* This approach is much faster for TypeScript compiler as it avoids recursive type operations.
|
|
17
|
+
*/
|
|
18
|
+
export type SitemapRouteResolvers<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
|
|
19
|
+
[K in keyof RouteList]: ((routeName: K) => Promise<RouteResolverResult>) | null;
|
|
14
20
|
};
|
|
15
|
-
export type SitemapGeneratorType
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
exhaustive: keyof RouteList extends never ? () => SitemapGeneratorType<Locales, RouteList> : MissingRoutesError<keyof RouteList>;
|
|
19
|
-
toXml: keyof RouteList extends never ? () => Promise<string> : MissingRoutesError<keyof RouteList>;
|
|
20
|
-
toJson: keyof RouteList extends never ? () => Promise<any> : MissingRoutesError<keyof RouteList>;
|
|
21
|
+
export type SitemapGeneratorType = {
|
|
22
|
+
toXml(): Promise<string>;
|
|
23
|
+
toJson(): Promise<string>;
|
|
21
24
|
};
|
|
22
25
|
export type SitemapGeneratorOptions = {
|
|
23
26
|
baseUrl?: string;
|
|
24
27
|
defaultPriority?: number;
|
|
25
28
|
defaultChangeFreq?: ChangeFrequency;
|
|
26
29
|
};
|
|
27
|
-
export declare
|
|
30
|
+
export declare class SitemapGenerator<Locales extends string[], RouteList extends RoutesDefinition<Locales>> {
|
|
31
|
+
private routeResolvers;
|
|
32
|
+
private options;
|
|
33
|
+
constructor(resolvers: SitemapRouteResolvers<Locales, RouteList>, options?: SitemapGeneratorOptions);
|
|
34
|
+
private getSitemapItems;
|
|
35
|
+
toXml(): Promise<string>;
|
|
36
|
+
toJson(): Promise<string>;
|
|
37
|
+
}
|
|
28
38
|
export {};
|
package/sitemap-generator.js
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.SitemapGenerator = void 0;
|
|
4
4
|
const object_to_xml_1 = require("./utils/object-to-xml");
|
|
5
5
|
function filterNullish(val) {
|
|
6
6
|
return val.filter((item) => item !== null && item !== undefined);
|
|
7
7
|
}
|
|
8
8
|
class SitemapGenerator {
|
|
9
|
-
constructor(options) {
|
|
10
|
-
this.routeResolvers =
|
|
11
|
-
this.options = null;
|
|
12
|
-
this.exhaustive = () => this;
|
|
13
|
-
this.toXml = () => {
|
|
14
|
-
return this.getSitemapItems().then((sitemapItems) => {
|
|
15
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
16
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
|
17
|
-
${sitemapItems.map((item) => ` <url>\n${(0, object_to_xml_1.objectToXml)(item)}\n </url>`).join("\n")}
|
|
18
|
-
</urlset>`;
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
this.toJson = () => this.getSitemapItems().then(JSON.stringify);
|
|
22
|
-
this.options = options !== null && options !== void 0 ? options : null;
|
|
9
|
+
constructor(resolvers, options) {
|
|
10
|
+
this.routeResolvers = resolvers;
|
|
11
|
+
this.options = options !== null && options !== void 0 ? options : {};
|
|
23
12
|
}
|
|
24
13
|
async getSitemapItems() {
|
|
25
14
|
const routes = Object.keys(this.routeResolvers);
|
|
26
|
-
const
|
|
15
|
+
const resolversWithRoutes = routes
|
|
16
|
+
.map((route) => ({ route, resolver: this.routeResolvers[route] }))
|
|
17
|
+
.filter(({ resolver }) => resolver !== null);
|
|
18
|
+
const allSitemapItems = await Promise.all(resolversWithRoutes.map(({ route, resolver }) => resolver(route))).then((resolverResults) => {
|
|
27
19
|
const sitemapItems = [];
|
|
28
20
|
resolverResults.forEach((result) => {
|
|
29
21
|
if (!result) {
|
|
@@ -38,22 +30,25 @@ ${sitemapItems.map((item) => ` <url>\n${(0, object_to_xml_1.objectToXml)(item
|
|
|
38
30
|
return sitemapItems;
|
|
39
31
|
});
|
|
40
32
|
return allSitemapItems.map((sitemapItem) => {
|
|
41
|
-
var _a
|
|
33
|
+
var _a;
|
|
42
34
|
return ({
|
|
43
35
|
lastmod: sitemapItem.lastmod,
|
|
44
|
-
loc: ((
|
|
45
|
-
changefreq: sitemapItem.changefreq !== undefined ? sitemapItem.changefreq :
|
|
46
|
-
priority: sitemapItem.priority !== undefined ? sitemapItem.priority :
|
|
36
|
+
loc: ((_a = this.options.baseUrl) !== null && _a !== void 0 ? _a : "") + sitemapItem.loc,
|
|
37
|
+
changefreq: sitemapItem.changefreq !== undefined ? sitemapItem.changefreq : this.options.defaultChangeFreq,
|
|
38
|
+
priority: sitemapItem.priority !== undefined ? sitemapItem.priority : this.options.defaultPriority,
|
|
47
39
|
});
|
|
48
40
|
});
|
|
49
41
|
}
|
|
50
|
-
|
|
51
|
-
this.
|
|
52
|
-
|
|
42
|
+
toXml() {
|
|
43
|
+
return this.getSitemapItems().then((sitemapItems) => {
|
|
44
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
45
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
|
46
|
+
${sitemapItems.map((item) => ` <url>\n${(0, object_to_xml_1.objectToXml)(item)}\n </url>`).join("\n")}
|
|
47
|
+
</urlset>`;
|
|
48
|
+
});
|
|
53
49
|
}
|
|
54
|
-
|
|
55
|
-
return this;
|
|
50
|
+
toJson() {
|
|
51
|
+
return this.getSitemapItems().then(JSON.stringify);
|
|
56
52
|
}
|
|
57
53
|
}
|
|
58
|
-
|
|
59
|
-
exports.createSitemapGenerator = createSitemapGenerator;
|
|
54
|
+
exports.SitemapGenerator = SitemapGenerator;
|
|
@@ -9,6 +9,9 @@ const { createSitemapGenerator, routeToUrl } = (0, router_1.createRouter)({
|
|
|
9
9
|
queryParam: (0, superstruct_1.optional)((0, superstruct_1.string)()),
|
|
10
10
|
}),
|
|
11
11
|
},
|
|
12
|
+
skippedRoute: {
|
|
13
|
+
path: "/skipped",
|
|
14
|
+
},
|
|
12
15
|
catchAllSegments: {
|
|
13
16
|
path: "/catch-all/[...pathParams]",
|
|
14
17
|
schema: (0, superstruct_1.object)({
|
|
@@ -26,18 +29,21 @@ const { createSitemapGenerator, routeToUrl } = (0, router_1.createRouter)({
|
|
|
26
29
|
}, { baseUrl: "https://www.uxf.cz" });
|
|
27
30
|
const BASE_URL = "www.uxf.cz";
|
|
28
31
|
test("routeToUrl", async () => {
|
|
29
|
-
const xml = await createSitemapGenerator({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const xml = await createSitemapGenerator({
|
|
33
|
+
index: async (route) => ({ loc: routeToUrl(route, { queryParam: "test" }) }),
|
|
34
|
+
catchAllSegments: async (route) => ({ loc: routeToUrl(route, { pathParams: ["param-1"] }) }),
|
|
35
|
+
optionalCatchAllSegments: async (route) => ({
|
|
36
|
+
loc: routeToUrl(route, { pathParams: ["param-1", "param-2"] }),
|
|
37
|
+
priority: 10,
|
|
38
|
+
}),
|
|
39
|
+
skippedRoute: null,
|
|
40
|
+
}, {
|
|
41
|
+
baseUrl: BASE_URL,
|
|
42
|
+
}).toXml();
|
|
37
43
|
expect(xml).toBe(`<?xml version="1.0" encoding="UTF-8"?>
|
|
38
44
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
|
39
45
|
<url>
|
|
40
|
-
<loc>${BASE_URL}
|
|
46
|
+
<loc>${BASE_URL}/?queryParam=test</loc>
|
|
41
47
|
</url>
|
|
42
48
|
<url>
|
|
43
49
|
<loc>${BASE_URL}/catch-all/param-1</loc>
|
package/types.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { GetServerSideProps, GetStaticProps, PreviewData as NextPreviewData } from "next";
|
|
2
|
-
import { Struct } from "superstruct";
|
|
2
|
+
import { Infer, Struct } from "superstruct";
|
|
3
3
|
export type RoutesDefinition<Locales extends string[]> = Record<string, {
|
|
4
4
|
path: string | Record<Locales[number], string>;
|
|
5
5
|
schema?: Struct<any, any> | null;
|
|
6
6
|
}>;
|
|
7
|
-
export type QueryParams<RouteList
|
|
8
|
-
[key in keyof RouteList[Route]]: string | string[] | undefined;
|
|
7
|
+
export type QueryParams<RouteList extends RoutesDefinition<any>, Route extends keyof RouteList> = RouteList[Route]["schema"] extends null ? never : {
|
|
8
|
+
[key in keyof Infer<NonNullable<RouteList[Route]["schema"]>>]: string | string[] | undefined;
|
|
9
9
|
};
|
|
10
|
-
export type UxfGetStaticProps<RouteList
|
|
10
|
+
export type UxfGetStaticProps<RouteList extends RoutesDefinition<any>, Route extends keyof RouteList, Props extends {
|
|
11
11
|
[key: string]: any;
|
|
12
12
|
} = {
|
|
13
13
|
[key: string]: any;
|
|
14
14
|
}, PreviewData extends NextPreviewData = NextPreviewData> = GetStaticProps<Props, QueryParams<RouteList, Route>, PreviewData>;
|
|
15
|
-
export type UxfGetServerSideProps<RouteList
|
|
15
|
+
export type UxfGetServerSideProps<RouteList extends RoutesDefinition<any>, Route extends keyof RouteList, Props extends {
|
|
16
16
|
[key: string]: any;
|
|
17
17
|
} = {
|
|
18
18
|
[key: string]: any;
|