@pracht/cli 1.3.3 → 1.5.0
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/CHANGELOG.md +68 -0
- package/README.md +11 -0
- package/dist/{generate-V7pQExlW.mjs → generate-DTMte0kd.mjs} +2 -2
- package/dist/index.mjs +4 -3
- package/dist/{inspect-CFD3-dna.mjs → inspect-WF08uLOl.mjs} +1 -1
- package/dist/typegen-Bl4fYQIy.mjs +172 -0
- package/package.json +2 -2
- /package/dist/{verify-D0pGwcyf.mjs → verify-FwVbdlxh.mjs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
# @pracht/cli
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#153](https://github.com/JoviDeCroock/pracht/pull/153) [`39860bd`](https://github.com/JoviDeCroock/pracht/commit/39860bd31e8559916d8f81ffa6122ac4cf1cffd1) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - **Breaking:** Middleware is now wrap-around (Hono/Koa/Astro shape). The
|
|
8
|
+
`MiddlewareFn` signature changes from `(args) => MiddlewareResult` to
|
|
9
|
+
`(args, next) => Promise<Response>`.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// Before
|
|
13
|
+
export const middleware: MiddlewareFn = async ({ request }) => {
|
|
14
|
+
if (!hasSession(request)) return { redirect: "/login" };
|
|
15
|
+
return { context: { user: "jovi" } };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// After
|
|
19
|
+
import { redirect, type MiddlewareFn } from "@pracht/core";
|
|
20
|
+
|
|
21
|
+
export const middleware: MiddlewareFn = async (
|
|
22
|
+
{ context, request },
|
|
23
|
+
next
|
|
24
|
+
) => {
|
|
25
|
+
if (!hasSession(request)) return redirect("/login");
|
|
26
|
+
(context as { user?: string }).user = "jovi";
|
|
27
|
+
return next();
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Why: middleware can now wrap `try / catch / finally` around the rest of the
|
|
32
|
+
request, which is the standard shape for tracing, logging, and observability
|
|
33
|
+
libraries (Honeycomb, OpenTelemetry, Sentry). It also matches what users
|
|
34
|
+
arriving from honox / Hono / Astro / SvelteKit / Koa expect.
|
|
35
|
+
|
|
36
|
+
Migration notes:
|
|
37
|
+
|
|
38
|
+
- Replace `return { redirect: "/path" }` with `return redirect("/path")`
|
|
39
|
+
using the new `redirect` helper exported from `@pracht/core`.
|
|
40
|
+
- Replace `return { context: { ... } }` with direct mutation of
|
|
41
|
+
`args.context`. Context is shared by reference between middleware and
|
|
42
|
+
the loader/handler.
|
|
43
|
+
- Replace bare `return` (continue) with `return next()`.
|
|
44
|
+
- Middleware that returns a `Response` directly still works as a
|
|
45
|
+
short-circuit.
|
|
46
|
+
- The `MiddlewareResult` type is removed; `MiddlewareNext` is exported.
|
|
47
|
+
- One `AbortSignal` is now shared per request across all middleware and
|
|
48
|
+
the loader/handler instead of a fresh 30s timer per phase. This makes
|
|
49
|
+
long-running middleware count toward the same overall budget as the
|
|
50
|
+
loader/handler, which matches how most users reason about per-request
|
|
51
|
+
timeouts.
|
|
52
|
+
|
|
53
|
+
The CLI's `pracht generate middleware` scaffold emits the new signature.
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- Updated dependencies [[`39860bd`](https://github.com/JoviDeCroock/pracht/commit/39860bd31e8559916d8f81ffa6122ac4cf1cffd1), [`39860bd`](https://github.com/JoviDeCroock/pracht/commit/39860bd31e8559916d8f81ffa6122ac4cf1cffd1), [`51d0de1`](https://github.com/JoviDeCroock/pracht/commit/51d0de12bcda8a1cadd3749f56f03bac2e95c3a6), [`f4763b1`](https://github.com/JoviDeCroock/pracht/commit/f4763b13dc85c7310d9a737b77b708c03a61b57c)]:
|
|
58
|
+
- @pracht/core@0.8.0
|
|
59
|
+
|
|
60
|
+
## 1.4.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- [#139](https://github.com/JoviDeCroock/pracht/pull/139) [`97594bd`](https://github.com/JoviDeCroock/pracht/commit/97594bd57b14fd5b527de647ba254b77f77912ca) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - Add typed route href helpers, `<Link route="...">`, route-object `useNavigate()`, and `pracht typegen` for generated route id/param declarations.
|
|
65
|
+
|
|
66
|
+
### Patch Changes
|
|
67
|
+
|
|
68
|
+
- Updated dependencies [[`5578791`](https://github.com/JoviDeCroock/pracht/commit/5578791b3abd6c808f5af78d88224667f483b32c), [`5938cb5`](https://github.com/JoviDeCroock/pracht/commit/5938cb56dd053fc8725efae0b7392dd65866b37b), [`97594bd`](https://github.com/JoviDeCroock/pracht/commit/97594bd57b14fd5b527de647ba254b77f77912ca)]:
|
|
69
|
+
- @pracht/core@0.7.0
|
|
70
|
+
|
|
3
71
|
## 1.3.3
|
|
4
72
|
|
|
5
73
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -79,6 +79,17 @@ pracht inspect api --json
|
|
|
79
79
|
pracht inspect build --json
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
### `pracht typegen`
|
|
83
|
+
|
|
84
|
+
Generate `src/pracht-routes.d.ts` and `src/pracht-routes.ts` from the resolved
|
|
85
|
+
route graph for typed `<Link>`, route-object `useNavigate()`, and `href()`
|
|
86
|
+
helpers. Use `--check` in CI to fail when generated files are stale.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pracht typegen
|
|
90
|
+
pracht typegen --check
|
|
91
|
+
```
|
|
92
|
+
|
|
82
93
|
### `pracht doctor`
|
|
83
94
|
|
|
84
95
|
Validate the local app wiring across the whole project. Use `--json` for
|
|
@@ -71,8 +71,8 @@ function buildMiddlewareModuleSource() {
|
|
|
71
71
|
return [
|
|
72
72
|
"import type { MiddlewareFn } from \"@pracht/core\";",
|
|
73
73
|
"",
|
|
74
|
-
"export const middleware: MiddlewareFn = async (_args) => {",
|
|
75
|
-
" return;",
|
|
74
|
+
"export const middleware: MiddlewareFn = async (_args, next) => {",
|
|
75
|
+
" return next();",
|
|
76
76
|
"};",
|
|
77
77
|
""
|
|
78
78
|
].join("\n");
|
package/dist/index.mjs
CHANGED
|
@@ -44,9 +44,10 @@ runMain(defineCommand({
|
|
|
44
44
|
build: () => import("./build-B7_6RUBf.mjs").then((m) => m.default),
|
|
45
45
|
dev: () => import("./dev-BCFe3g38.mjs").then((m) => m.default),
|
|
46
46
|
doctor: () => import("./doctor-DyOWEA42.mjs").then((m) => m.default),
|
|
47
|
-
generate: () => import("./generate-
|
|
48
|
-
inspect: () => import("./inspect-
|
|
49
|
-
|
|
47
|
+
generate: () => import("./generate-DTMte0kd.mjs").then((m) => m.default),
|
|
48
|
+
inspect: () => import("./inspect-WF08uLOl.mjs").then((m) => m.default),
|
|
49
|
+
typegen: () => import("./typegen-Bl4fYQIy.mjs").then((m) => m.default),
|
|
50
|
+
verify: () => import("./verify-FwVbdlxh.mjs").then((m) => m.default)
|
|
50
51
|
}
|
|
51
52
|
}));
|
|
52
53
|
//#endregion
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { f as ensureTrailingNewline, n as displayPath, p as handleCliError } from "./project-voPTS9UC.mjs";
|
|
2
|
+
import { runInspect } from "./inspect-WF08uLOl.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
6
|
+
//#region src/commands/typegen.ts
|
|
7
|
+
const DEFAULT_DECLARATION_OUT = "src/pracht-routes.d.ts";
|
|
8
|
+
const DEFAULT_RUNTIME_OUT = "src/pracht-routes.ts";
|
|
9
|
+
var typegen_default = defineCommand({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "typegen",
|
|
12
|
+
description: "Generate typed route declarations and href helpers"
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
out: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: `Declaration output path (default: ${DEFAULT_DECLARATION_OUT})`
|
|
18
|
+
},
|
|
19
|
+
"runtime-out": {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: `Runtime href helper output path (default: ${DEFAULT_RUNTIME_OUT})`
|
|
22
|
+
},
|
|
23
|
+
check: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
description: "Check whether generated route files are up to date without writing"
|
|
26
|
+
},
|
|
27
|
+
json: {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
description: "Output as JSON"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
async run({ args }) {
|
|
33
|
+
const json = Boolean(args.json);
|
|
34
|
+
try {
|
|
35
|
+
const result = await runTypegen({
|
|
36
|
+
check: Boolean(args.check),
|
|
37
|
+
declarationOut: typeof args.out === "string" ? args.out : DEFAULT_DECLARATION_OUT,
|
|
38
|
+
root: process.cwd(),
|
|
39
|
+
runtimeOut: typeof args["runtime-out"] === "string" ? args["runtime-out"] : DEFAULT_RUNTIME_OUT
|
|
40
|
+
});
|
|
41
|
+
if (json) {
|
|
42
|
+
console.log(JSON.stringify({
|
|
43
|
+
ok: true,
|
|
44
|
+
...result
|
|
45
|
+
}, null, 2));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (result.check) {
|
|
49
|
+
console.log("Generated route files are up to date.");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log("Generated typed routes:");
|
|
53
|
+
for (const file of result.files) console.log(` ${file}`);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
handleCliError(error, { json });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
async function runTypegen(options) {
|
|
60
|
+
const report = await runInspect(options.root, { target: "routes" });
|
|
61
|
+
const routes = report.routes ?? [];
|
|
62
|
+
validateRoutes(routes);
|
|
63
|
+
const declarationPath = resolveOutputPath(options.root, options.declarationOut);
|
|
64
|
+
const runtimePath = resolveOutputPath(options.root, options.runtimeOut);
|
|
65
|
+
const outputs = [{
|
|
66
|
+
path: declarationPath,
|
|
67
|
+
source: buildDeclarationSource(routes)
|
|
68
|
+
}, {
|
|
69
|
+
path: runtimePath,
|
|
70
|
+
source: buildRuntimeSource(routes)
|
|
71
|
+
}];
|
|
72
|
+
if (options.check) {
|
|
73
|
+
const stale = outputs.filter((output) => !fileMatches(output.path, output.source));
|
|
74
|
+
if (stale.length > 0) {
|
|
75
|
+
const files = stale.map((output) => displayPath(options.root, output.path)).join(", ");
|
|
76
|
+
throw new Error(`Generated route files are out of date: ${files}. Run \`pracht typegen\`.`);
|
|
77
|
+
}
|
|
78
|
+
} else for (const output of outputs) {
|
|
79
|
+
mkdirSync(dirname(output.path), { recursive: true });
|
|
80
|
+
writeFileSync(output.path, output.source, "utf-8");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
check: options.check,
|
|
84
|
+
files: outputs.map((output) => displayPath(options.root, output.path)),
|
|
85
|
+
mode: report.mode,
|
|
86
|
+
routes: routes.length
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function validateRoutes(routes) {
|
|
90
|
+
const seen = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const route of routes) {
|
|
92
|
+
if (!route.id) throw new Error(`Route ${route.path} resolved without an id.`);
|
|
93
|
+
const previousPath = seen.get(route.id);
|
|
94
|
+
if (previousPath) throw new Error(`Duplicate route id "${route.id}" for ${previousPath} and ${route.path}. Add explicit unique ids.`);
|
|
95
|
+
seen.set(route.id, route.path);
|
|
96
|
+
inferRouteParams(route.path);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function buildDeclarationSource(routes) {
|
|
100
|
+
const lines = [
|
|
101
|
+
"// Generated by `pracht typegen`. Do not edit manually.",
|
|
102
|
+
"import \"@pracht/core\";",
|
|
103
|
+
"import type { RouteParamInput, SearchParamsInput } from \"@pracht/core\";",
|
|
104
|
+
"",
|
|
105
|
+
"declare module \"@pracht/core\" {",
|
|
106
|
+
" interface Register {",
|
|
107
|
+
" routes: {"
|
|
108
|
+
];
|
|
109
|
+
for (const route of routes) {
|
|
110
|
+
lines.push(` ${JSON.stringify(route.id)}: {`);
|
|
111
|
+
lines.push(` path: ${JSON.stringify(route.path)};`);
|
|
112
|
+
lines.push(` params: ${formatParamsType(inferRouteParams(route.path))};`);
|
|
113
|
+
lines.push(" search: SearchParamsInput;");
|
|
114
|
+
lines.push(" };");
|
|
115
|
+
}
|
|
116
|
+
lines.push(" };");
|
|
117
|
+
lines.push(" }");
|
|
118
|
+
lines.push("}");
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push("export {};");
|
|
121
|
+
lines.push("");
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
function buildRuntimeSource(routes) {
|
|
125
|
+
const lines = [
|
|
126
|
+
"// Generated by `pracht typegen`. Do not edit manually.",
|
|
127
|
+
"import { createHref } from \"@pracht/core\";",
|
|
128
|
+
"import type { HrefRouteDefinition } from \"@pracht/core\";",
|
|
129
|
+
"",
|
|
130
|
+
"export const routes = ["
|
|
131
|
+
];
|
|
132
|
+
for (const route of routes) {
|
|
133
|
+
lines.push(" {");
|
|
134
|
+
lines.push(` id: ${JSON.stringify(route.id)},`);
|
|
135
|
+
lines.push(` path: ${JSON.stringify(route.path)},`);
|
|
136
|
+
lines.push(" },");
|
|
137
|
+
}
|
|
138
|
+
lines.push("] as const satisfies readonly HrefRouteDefinition[];");
|
|
139
|
+
lines.push("");
|
|
140
|
+
lines.push("export const href = createHref(routes);");
|
|
141
|
+
lines.push("");
|
|
142
|
+
return lines.join("\n");
|
|
143
|
+
}
|
|
144
|
+
function inferRouteParams(path) {
|
|
145
|
+
const params = [];
|
|
146
|
+
const seen = /* @__PURE__ */ new Set();
|
|
147
|
+
for (const segment of path.split("/").filter(Boolean)) {
|
|
148
|
+
let name = null;
|
|
149
|
+
if (segment === "*") name = "*";
|
|
150
|
+
else if (segment.startsWith(":")) name = segment.endsWith("*") ? segment.slice(1, -1) || "*" : segment.slice(1);
|
|
151
|
+
if (!name) continue;
|
|
152
|
+
if (seen.has(name)) throw new Error(`Route ${path} declares duplicate param "${name}".`);
|
|
153
|
+
seen.add(name);
|
|
154
|
+
params.push(name);
|
|
155
|
+
}
|
|
156
|
+
return params;
|
|
157
|
+
}
|
|
158
|
+
function formatParamsType(params) {
|
|
159
|
+
if (params.length === 0) return "Record<never, never>";
|
|
160
|
+
return `{ ${params.map((param) => `${JSON.stringify(param)}: RouteParamInput;`).join(" ")} }`;
|
|
161
|
+
}
|
|
162
|
+
function resolveOutputPath(root, outputPath) {
|
|
163
|
+
const absolutePath = isAbsolute(outputPath) ? outputPath : resolve(root, outputPath);
|
|
164
|
+
const relativePath = relative(root, absolutePath);
|
|
165
|
+
if (relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath)) return absolutePath;
|
|
166
|
+
throw new Error(`Refusing to write outside the project root: ${outputPath}.`);
|
|
167
|
+
}
|
|
168
|
+
function fileMatches(path, source) {
|
|
169
|
+
return existsSync(path) && readFileSync(path, "utf-8") === ensureTrailingNewline(source);
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
export { typegen_default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CLI for developing, building, validating, inspecting, and scaffolding Pracht apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pracht",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"citty": "^0.1.6",
|
|
42
|
-
"@pracht/core": "0.
|
|
42
|
+
"@pracht/core": "0.8.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsdown"
|
|
File without changes
|