@mpen/rerouter 0.1.9 → 0.3.1
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/README.md +80 -18
- package/cli/bin.test.ts +221 -0
- package/cli/bin.ts +342 -0
- package/cli/fixtures/bin/kitchen-sink.tsx +15 -0
- package/cli/fixtures/bin/optional.tsx +3 -0
- package/cli/fixtures/bin/pages/Home.tsx +3 -0
- package/cli/fixtures/bin/pages/KitchenSink.tsx +3 -0
- package/cli/fixtures/bin/pages/Login.tsx +3 -0
- package/cli/fixtures/bin/pages/Match.tsx +3 -0
- package/cli/fixtures/bin/pages/NotFound.tsx +3 -0
- package/cli/fixtures/bin/pages/Optional.tsx +3 -0
- package/cli/fixtures/bin/regexp-groups.tsx +11 -0
- package/cli/fixtures/bin/simple.tsx +1 -0
- package/cli/fixtures/bin/unnamed.tsx +4 -0
- package/cli/tsconfig.json +9 -0
- package/dist/acorn-k7ED_tOl.js +4968 -0
- package/dist/angular--Iqdw9UJ.js +4057 -0
- package/dist/babel-hfWAujRY.js +9878 -0
- package/dist/bin.d.ts +29 -0
- package/dist/bin.js +233 -0
- package/dist/estree-C1Zjnvlw.js +7266 -0
- package/dist/flow-BaD9LyIP.js +52912 -0
- package/dist/glimmer-CvCjW_1V.js +7541 -0
- package/dist/graphql-BdtzBuWh.js +1945 -0
- package/dist/html-DkZtUVbo.js +7137 -0
- package/dist/index.d.ts +278 -0
- package/dist/index.js +247 -0
- package/dist/markdown-Z8Vrc69e.js +6876 -0
- package/dist/meriyah-DeO4stuH.js +7590 -0
- package/dist/postcss-BmgGJ0E5.js +6777 -0
- package/dist/prettier-BT_F8kIx.js +15629 -0
- package/dist/routes-PW-bNm8e.js +135 -0
- package/dist/typescript-DtIxStjy.js +22936 -0
- package/dist/yaml-CWOPBY0q.js +5281 -0
- package/examples/App.tsx +80 -0
- package/examples/dist/BlogPost-c10d9w2p.js +1 -0
- package/examples/dist/FetchLoading-534mdrgz.js +1 -0
- package/examples/dist/FetchLoading-sbxbdkre.js +1 -0
- package/examples/dist/Home-a1258p25.js +1 -0
- package/examples/dist/KitchenSink-821mjg0h.js +1 -0
- package/examples/dist/Login-wywx6bp7.js +1 -0
- package/examples/dist/Match-1e72jm5w.js +1 -0
- package/examples/dist/NotFound-smxj24jw.js +1 -0
- package/examples/dist/SlowLoading-59xxmbfk.js +1 -0
- package/examples/dist/index-0d4kj0rv.js +2 -0
- package/examples/dist/index-3x197sbt.js +9 -0
- package/examples/dist/index-a2hkfx1n.js +9 -0
- package/examples/dist/index-d21me1mc.js +9 -0
- package/examples/dist/index-ktqdknsn.js +2 -0
- package/examples/dist/index-p53qxxzd.js +2 -0
- package/examples/dist/index.html +67 -0
- package/examples/index.html +67 -0
- package/examples/pages/BlogPost.tsx +17 -0
- package/examples/pages/FetchLoading.tsx +53 -0
- package/examples/pages/FetchLoadingItem.tsx +45 -0
- package/examples/pages/Home.tsx +3 -0
- package/examples/pages/KitchenSink.tsx +23 -0
- package/examples/pages/Login.tsx +3 -0
- package/examples/pages/Match.tsx +5 -0
- package/examples/pages/NotFound.tsx +3 -0
- package/examples/pages/SlowLoading.tsx +8 -0
- package/examples/routes.gen.ts +105 -0
- package/examples/routes.ts +40 -0
- package/examples/server/serve-dist.ts +33 -0
- package/examples/server/tsconfig.json +9 -0
- package/package.json +41 -31
- package/src/components/Link.test.tsx +139 -0
- package/src/components/Link.tsx +89 -0
- package/src/components/NavLink.test.tsx +119 -0
- package/src/components/NavLink.tsx +71 -0
- package/src/components/Router.test.tsx +183 -0
- package/src/components/Router.tsx +207 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useUrl.ts +22 -0
- package/src/index.ts +6 -0
- package/src/lib/mergeSearch.test.ts +37 -0
- package/src/lib/mergeSearch.ts +21 -0
- package/src/lib/routes.test.ts +67 -0
- package/src/lib/routes.ts +247 -0
- package/src/lib/url.ts +9 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +21 -0
- package/LICENSE +0 -21
- package/dist/bundle.cjs +0 -422
- package/dist/bundle.d.ts +0 -2
- package/dist/bundle.mjs +0 -420
- package/dist/dev.d.ts +0 -1
- package/dist/log.d.ts +0 -1
- package/dist/uri-template.d.ts +0 -56
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region cli/bin.d.ts
|
|
2
|
+
type RunOptions = {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
commandName?: string;
|
|
5
|
+
commandArgs?: readonly string[];
|
|
6
|
+
};
|
|
7
|
+
type RunResult = {
|
|
8
|
+
exitCode?: number;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Runs the rerouter CLI implementation without spawning a separate process.
|
|
14
|
+
*
|
|
15
|
+
* @param args - Command line arguments, excluding the binary name.
|
|
16
|
+
* @param options - Runtime options used to resolve paths and render the command comment.
|
|
17
|
+
* @returns Captured stdout, stderr, and an optional process exit code.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const result = await runRerouterBin(['./routes.ts'])
|
|
22
|
+
* process.stdout.write(result.stdout)
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
declare function runRerouterBin(args: readonly string[], options?: RunOptions): Promise<RunResult>;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { runRerouterBin };
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as normalizeRoutes, t as normalizeLegacyPathToRegexpSyntax } from "./routes-PW-bNm8e.js";
|
|
3
|
+
import { parse } from "path-to-regexp";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
9
|
+
//#region cli/bin.ts
|
|
10
|
+
const PARSE_CONFIG = {
|
|
11
|
+
options: {
|
|
12
|
+
output: {
|
|
13
|
+
type: "string",
|
|
14
|
+
short: "o"
|
|
15
|
+
},
|
|
16
|
+
write: {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
short: "w"
|
|
19
|
+
},
|
|
20
|
+
pretty: {
|
|
21
|
+
type: "boolean",
|
|
22
|
+
short: "p"
|
|
23
|
+
},
|
|
24
|
+
"wildcard-delimiter": { type: "string" },
|
|
25
|
+
"encode-function": { type: "string" }
|
|
26
|
+
},
|
|
27
|
+
allowPositionals: true,
|
|
28
|
+
strict: true
|
|
29
|
+
};
|
|
30
|
+
function escapeString(value) {
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
}
|
|
33
|
+
function shellEscape(arg) {
|
|
34
|
+
if (/^[a-z0-9/_.-]+$/i.test(arg)) return arg;
|
|
35
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
36
|
+
}
|
|
37
|
+
function compilePathGenerator(pattern, { delimiter = "/", encode = "encodeURIComponent", functionName = "generate" } = {}) {
|
|
38
|
+
const { tokens } = parse(normalizeLegacyPathToRegexpSyntax(pattern));
|
|
39
|
+
const baseProps = [];
|
|
40
|
+
const groupTypes = [];
|
|
41
|
+
const typeOfParam = (t) => t.type === "wildcard" ? "WildcardType" : "ParamType";
|
|
42
|
+
const makeProp = (name, t) => ({
|
|
43
|
+
name,
|
|
44
|
+
type: typeOfParam(t)
|
|
45
|
+
});
|
|
46
|
+
const renderPropsType = (props) => props.length ? `{ ${props.map((p) => `${escapeString(p.name)}: ${p.type}`).join("; ")} }` : "{}";
|
|
47
|
+
function collectGroupProps(ts2) {
|
|
48
|
+
const props = [];
|
|
49
|
+
for (const t of ts2) if (t.type === "param" || t.type === "wildcard") props.push(makeProp(t.name, t));
|
|
50
|
+
else if (t.type === "group") props.push(...collectGroupProps(t.tokens));
|
|
51
|
+
return props;
|
|
52
|
+
}
|
|
53
|
+
function collectTypes(ts2, intoBase = true) {
|
|
54
|
+
for (const t of ts2) if ((t.type === "param" || t.type === "wildcard") && intoBase) baseProps.push(makeProp(t.name, t));
|
|
55
|
+
else if (t.type === "group") {
|
|
56
|
+
const groupProps = collectGroupProps(t.tokens);
|
|
57
|
+
if (groupProps.length) groupTypes.push(`AllOrNone<${renderPropsType(groupProps)}>`);
|
|
58
|
+
collectTypes(t.tokens, false);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
collectTypes(tokens);
|
|
62
|
+
const baseParamsType = renderPropsType(baseProps);
|
|
63
|
+
const paramsType = groupTypes.length ? `${baseParamsType} & ${groupTypes.join(" & ")}` : baseParamsType;
|
|
64
|
+
const lines = [];
|
|
65
|
+
const indentUnit = " ";
|
|
66
|
+
const line = (indentLevel, text = "") => {
|
|
67
|
+
if (text === "") lines.push("");
|
|
68
|
+
else lines.push(indentUnit.repeat(indentLevel) + text);
|
|
69
|
+
};
|
|
70
|
+
const hasAnyParams = baseProps.length > 0 || groupTypes.length > 0;
|
|
71
|
+
if (hasAnyParams) lines.push(`export function ${functionName}(params: ${paramsType}): string {`);
|
|
72
|
+
else lines.push(`export function ${functionName}(): string {`);
|
|
73
|
+
line(1, `let sb = ""`);
|
|
74
|
+
line(0);
|
|
75
|
+
const delim = escapeString(delimiter);
|
|
76
|
+
function collectNames(ts2) {
|
|
77
|
+
const names = [];
|
|
78
|
+
for (const t of ts2) if (t.type === "param" || t.type === "wildcard") names.push(t.name);
|
|
79
|
+
else if (t.type === "group") names.push(...collectNames(t.tokens));
|
|
80
|
+
return names;
|
|
81
|
+
}
|
|
82
|
+
function emitTokens(ts2, indentLevel, optional = false) {
|
|
83
|
+
if (!optional && hasAnyParams) {
|
|
84
|
+
for (const t of ts2) if (t.type === "param" || t.type === "wildcard") line(indentLevel, `if (params[${escapeString(t.name)}] == null) throw new Error(${escapeString(`Missing param: ${t.name}`)})`);
|
|
85
|
+
}
|
|
86
|
+
for (const t of ts2) if (t.type === "text") line(indentLevel, `sb += ${escapeString(t.value)}`);
|
|
87
|
+
else if (t.type === "param") line(indentLevel, `sb += (${encode})(String(params[${escapeString(t.name)}]))`);
|
|
88
|
+
else if (t.type === "wildcard") line(indentLevel, `sb += Array.from(params[${escapeString(t.name)}], v => (${encode})(String(v))).join(${delim})`);
|
|
89
|
+
else if (t.type === "group") {
|
|
90
|
+
const names = collectNames(t.tokens).map((name) => escapeString(name));
|
|
91
|
+
if (!names.length) continue;
|
|
92
|
+
const all = names.map((n) => `params[${n}] != null`).join(" && ");
|
|
93
|
+
const none = names.map((n) => `params[${n}] == null`).join(" && ");
|
|
94
|
+
const list = names.join(", ");
|
|
95
|
+
line(indentLevel, `if (${all}) {`);
|
|
96
|
+
emitTokens(t.tokens, indentLevel + 1, true);
|
|
97
|
+
line(indentLevel, `} else if (!(${none})) {`);
|
|
98
|
+
line(indentLevel + 1, `throw new Error(${escapeString(`Group requires all-or-none: ${list}`)})`);
|
|
99
|
+
line(indentLevel, `}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
emitTokens(tokens, 1);
|
|
103
|
+
line(0);
|
|
104
|
+
line(1, `return sb`);
|
|
105
|
+
lines.push(`}`);
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
function toRouteFunctionName(routeName) {
|
|
109
|
+
return routeName.trim().replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[^a-zA-Z_]+/, "") || "route";
|
|
110
|
+
}
|
|
111
|
+
async function importRoutes(routesPath) {
|
|
112
|
+
const mod = await import(pathToFileURL(routesPath).href);
|
|
113
|
+
if (!Array.isArray(mod.default)) throw new Error("Routes file must default export an array of routes.");
|
|
114
|
+
return mod.default;
|
|
115
|
+
}
|
|
116
|
+
async function formatWithPrettier(source, outputPath) {
|
|
117
|
+
let prettier;
|
|
118
|
+
try {
|
|
119
|
+
prettier = await import("./prettier-BT_F8kIx.js");
|
|
120
|
+
} catch (cause) {
|
|
121
|
+
throw new Error("The --pretty option requires prettier to be installed.", { cause });
|
|
122
|
+
}
|
|
123
|
+
const options = await prettier.resolveConfig(outputPath) ?? {};
|
|
124
|
+
return prettier.format(source, {
|
|
125
|
+
...options,
|
|
126
|
+
filepath: outputPath
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function extractRoutes(routes) {
|
|
130
|
+
return normalizeRoutes(routes).flatMap((route) => {
|
|
131
|
+
if (!route.name || typeof route.pattern !== "string") return [];
|
|
132
|
+
return [{
|
|
133
|
+
name: route.name,
|
|
134
|
+
pattern: route.pattern
|
|
135
|
+
}];
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async function main(options, positionals, { cwd = process.cwd(), commandName = "rerouter", commandArgs = process.argv.slice(2) } = {}) {
|
|
139
|
+
const [routesPathArg] = positionals;
|
|
140
|
+
if (!routesPathArg) return {
|
|
141
|
+
exitCode: 1,
|
|
142
|
+
stdout: "",
|
|
143
|
+
stderr: "Usage: rerouter <routes-file> [-o <output-file>] [-w] [-p|--pretty] [--wildcard-delimiter <string>] [--encode-function <identifier>]\n"
|
|
144
|
+
};
|
|
145
|
+
const routesPath = path.resolve(cwd, routesPathArg);
|
|
146
|
+
let outputPath;
|
|
147
|
+
if (options.output) outputPath = path.resolve(cwd, options.output);
|
|
148
|
+
else if (options.write) outputPath = path.join(path.dirname(routesPath), path.basename(routesPath, path.extname(routesPath)) + ".gen.ts");
|
|
149
|
+
const routes = extractRoutes(await importRoutes(routesPath)).map((r) => ({
|
|
150
|
+
...r,
|
|
151
|
+
pattern: r.pattern.trim()
|
|
152
|
+
})).filter((r) => r.pattern.startsWith("/") && r.pattern !== "*");
|
|
153
|
+
const wildcardDelimiter = options["wildcard-delimiter"] ?? "/";
|
|
154
|
+
const encodeFunction = options["encode-function"] ?? "encodeURIComponent";
|
|
155
|
+
const commandText = [commandName, ...commandArgs.map(shellEscape)].join(" ");
|
|
156
|
+
const out = [];
|
|
157
|
+
out.push(`// Do not modify this file. It was auto-generated with the following command:`);
|
|
158
|
+
out.push(`// $ ${commandText}`);
|
|
159
|
+
out.push(``);
|
|
160
|
+
out.push(`type AllOrNone<T> =`);
|
|
161
|
+
out.push(` | Required<T>`);
|
|
162
|
+
out.push(` | { [K in keyof T]?: never }`);
|
|
163
|
+
out.push(``);
|
|
164
|
+
out.push(`type ParamType = string | number | boolean`);
|
|
165
|
+
out.push(`type WildcardType = Iterable<ParamType>`);
|
|
166
|
+
out.push(``);
|
|
167
|
+
if (!routes.length) {
|
|
168
|
+
out.push(`// No string route patterns found in the default export.`);
|
|
169
|
+
out.push(``);
|
|
170
|
+
} else {
|
|
171
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
172
|
+
for (const route of routes) {
|
|
173
|
+
const base = toRouteFunctionName(route.name);
|
|
174
|
+
let name = base;
|
|
175
|
+
let i = 2;
|
|
176
|
+
while (usedNames.has(name)) name = `${base}_${i++}`;
|
|
177
|
+
usedNames.add(name);
|
|
178
|
+
out.push(compilePathGenerator(route.pattern, {
|
|
179
|
+
functionName: name,
|
|
180
|
+
delimiter: wildcardDelimiter,
|
|
181
|
+
encode: encodeFunction
|
|
182
|
+
}));
|
|
183
|
+
out.push(``);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
let finalOutput = out.join("\n");
|
|
187
|
+
if (outputPath) {
|
|
188
|
+
if (options.pretty) finalOutput = await formatWithPrettier(finalOutput, outputPath);
|
|
189
|
+
await fs.writeFile(outputPath, finalOutput, "utf8");
|
|
190
|
+
return {
|
|
191
|
+
stdout: "",
|
|
192
|
+
stderr: `Wrote ${path.relative(cwd, outputPath) || "."}\n`
|
|
193
|
+
};
|
|
194
|
+
} else return {
|
|
195
|
+
stdout: finalOutput,
|
|
196
|
+
stderr: ""
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Runs the rerouter CLI implementation without spawning a separate process.
|
|
201
|
+
*
|
|
202
|
+
* @param args - Command line arguments, excluding the binary name.
|
|
203
|
+
* @param options - Runtime options used to resolve paths and render the command comment.
|
|
204
|
+
* @returns Captured stdout, stderr, and an optional process exit code.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const result = await runRerouterBin(['./routes.ts'])
|
|
209
|
+
* process.stdout.write(result.stdout)
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
async function runRerouterBin(args, options = {}) {
|
|
215
|
+
const { values, positionals } = parseArgs({
|
|
216
|
+
...PARSE_CONFIG,
|
|
217
|
+
args: [...args]
|
|
218
|
+
});
|
|
219
|
+
return main(values, positionals, {
|
|
220
|
+
...options,
|
|
221
|
+
commandArgs: args
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) runRerouterBin(process.argv.slice(2)).then((result) => {
|
|
225
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
226
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
227
|
+
if (typeof result.exitCode === "number") process.exitCode = result.exitCode;
|
|
228
|
+
}, (err) => {
|
|
229
|
+
console.error(err ?? "An unknown error occurred");
|
|
230
|
+
process.exitCode = 1;
|
|
231
|
+
});
|
|
232
|
+
//#endregion
|
|
233
|
+
export { runRerouterBin };
|