@pracht/cli 1.1.5 → 1.2.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 +11 -0
- package/bin/pracht.js +1 -40
- package/dist/build-BzQAQjuy.mjs +212 -0
- package/dist/build-metadata-IABPFFKk.mjs +49 -0
- package/dist/dev-BCFe3g38.mjs +25 -0
- package/dist/doctor-BcoqyBk-.mjs +25 -0
- package/dist/generate-BXkePCIx.mjs +404 -0
- package/dist/index.mjs +41 -0
- package/dist/inspect-DIjjCfs3.mjs +128 -0
- package/dist/manifest-DGq1n5LT.mjs +99 -0
- package/dist/project-DydjqBoS.mjs +155 -0
- package/dist/verification-Dfl3X4Zo.mjs +372 -0
- package/dist/verify-CObGxWtW.mjs +31 -0
- package/package.json +6 -2
- package/{lib/build-metadata.js → src/build-metadata.ts} +22 -9
- package/{lib/build-shared.js → src/build-shared.ts} +39 -13
- package/src/commands/build.ts +132 -0
- package/src/commands/dev.ts +27 -0
- package/src/commands/doctor.ts +33 -0
- package/{lib/commands/generate.js → src/commands/generate.ts} +178 -72
- package/{lib/commands/inspect.js → src/commands/inspect.ts} +81 -30
- package/src/commands/verify.ts +37 -0
- package/{lib/constants.js → src/constants.ts} +12 -2
- package/src/index.ts +21 -0
- package/{lib/manifest.js → src/manifest.ts} +32 -13
- package/{lib/project.js → src/project.ts} +48 -20
- package/src/utils.ts +72 -0
- package/{lib/verification.js → src/verification.ts} +103 -34
- package/tsdown.config.ts +8 -0
- package/lib/cli.js +0 -164
- package/lib/commands/build.js +0 -120
- package/lib/commands/dev.js +0 -16
- package/lib/commands/doctor.js +0 -21
- package/lib/commands/verify.js +0 -21
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { a as toManifestModulePath, i as insertArrayItem, n as extractRegistryEntries, o as upsertObjectEntry, t as ensureCoreNamedImport } from "./manifest-DGq1n5LT.mjs";
|
|
2
|
+
import { _ as requireEnum, a as readProjectConfig, c as resolveProjectPath, d as writeGeneratedFile, f as ensureTrailingNewline, g as quote, h as parseCommaList, l as resolveRouteModulePath, m as parseApiMethods, n as displayPath, o as resolveApiModulePath, s as resolvePagesRouteModulePath, t as assertFileExists, u as resolveScopedFile, v as requirePositiveInteger } from "./project-DydjqBoS.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
var generate_default = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "generate",
|
|
8
|
+
description: "Scaffold framework files"
|
|
9
|
+
},
|
|
10
|
+
subCommands: {
|
|
11
|
+
route: defineCommand({
|
|
12
|
+
meta: {
|
|
13
|
+
name: "route",
|
|
14
|
+
description: "Scaffold a route module"
|
|
15
|
+
},
|
|
16
|
+
args: {
|
|
17
|
+
path: {
|
|
18
|
+
type: "string",
|
|
19
|
+
required: true,
|
|
20
|
+
description: "Route path (e.g. /dashboard)"
|
|
21
|
+
},
|
|
22
|
+
render: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Render mode: ssr, spa, ssg, or isg"
|
|
25
|
+
},
|
|
26
|
+
shell: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Shell name"
|
|
29
|
+
},
|
|
30
|
+
middleware: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Middleware names (comma-separated)"
|
|
33
|
+
},
|
|
34
|
+
loader: {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description: "Include loader"
|
|
37
|
+
},
|
|
38
|
+
"error-boundary": {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
description: "Include error boundary"
|
|
41
|
+
},
|
|
42
|
+
"static-paths": {
|
|
43
|
+
type: "boolean",
|
|
44
|
+
description: "Include static paths"
|
|
45
|
+
},
|
|
46
|
+
title: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Page title"
|
|
49
|
+
},
|
|
50
|
+
revalidate: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "ISG revalidation seconds"
|
|
53
|
+
},
|
|
54
|
+
json: {
|
|
55
|
+
type: "boolean",
|
|
56
|
+
description: "Output as JSON"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
async run({ args }) {
|
|
60
|
+
outputResult(generateRoute(args, readProjectConfig(process.cwd())), Boolean(args.json));
|
|
61
|
+
}
|
|
62
|
+
}),
|
|
63
|
+
shell: defineCommand({
|
|
64
|
+
meta: {
|
|
65
|
+
name: "shell",
|
|
66
|
+
description: "Scaffold a shell component"
|
|
67
|
+
},
|
|
68
|
+
args: {
|
|
69
|
+
name: {
|
|
70
|
+
type: "string",
|
|
71
|
+
required: true,
|
|
72
|
+
description: "Shell name"
|
|
73
|
+
},
|
|
74
|
+
json: {
|
|
75
|
+
type: "boolean",
|
|
76
|
+
description: "Output as JSON"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
async run({ args }) {
|
|
80
|
+
const project = readProjectConfig(process.cwd());
|
|
81
|
+
outputResult(generateShell(args.name, project), Boolean(args.json));
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
middleware: defineCommand({
|
|
85
|
+
meta: {
|
|
86
|
+
name: "middleware",
|
|
87
|
+
description: "Scaffold a middleware function"
|
|
88
|
+
},
|
|
89
|
+
args: {
|
|
90
|
+
name: {
|
|
91
|
+
type: "string",
|
|
92
|
+
required: true,
|
|
93
|
+
description: "Middleware name"
|
|
94
|
+
},
|
|
95
|
+
json: {
|
|
96
|
+
type: "boolean",
|
|
97
|
+
description: "Output as JSON"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
async run({ args }) {
|
|
101
|
+
const project = readProjectConfig(process.cwd());
|
|
102
|
+
outputResult(generateMiddleware(args.name, project), Boolean(args.json));
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
105
|
+
api: defineCommand({
|
|
106
|
+
meta: {
|
|
107
|
+
name: "api",
|
|
108
|
+
description: "Scaffold an API route"
|
|
109
|
+
},
|
|
110
|
+
args: {
|
|
111
|
+
path: {
|
|
112
|
+
type: "string",
|
|
113
|
+
required: true,
|
|
114
|
+
description: "API endpoint path"
|
|
115
|
+
},
|
|
116
|
+
methods: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "HTTP methods (comma-separated, e.g. GET,POST)"
|
|
119
|
+
},
|
|
120
|
+
json: {
|
|
121
|
+
type: "boolean",
|
|
122
|
+
description: "Output as JSON"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
async run({ args }) {
|
|
126
|
+
outputResult(generateApi(args, readProjectConfig(process.cwd())), Boolean(args.json));
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
function outputResult(result, json) {
|
|
132
|
+
if (json) {
|
|
133
|
+
console.log(JSON.stringify({
|
|
134
|
+
ok: true,
|
|
135
|
+
...result
|
|
136
|
+
}, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
console.log(`Created ${result.kind}:`);
|
|
140
|
+
for (const file of result.created) console.log(` ${file}`);
|
|
141
|
+
for (const file of result.updated) console.log(` updated ${file}`);
|
|
142
|
+
}
|
|
143
|
+
function generateRoute(args, project) {
|
|
144
|
+
const routePath = normalizeRoutePathString(args.path);
|
|
145
|
+
const render = requireEnum(args.render, "render", [
|
|
146
|
+
"spa",
|
|
147
|
+
"ssr",
|
|
148
|
+
"ssg",
|
|
149
|
+
"isg"
|
|
150
|
+
], "ssr");
|
|
151
|
+
const includeLoader = Boolean(args.loader);
|
|
152
|
+
const includeErrorBoundary = Boolean(args["error-boundary"]);
|
|
153
|
+
const middleware = parseCommaList(args.middleware);
|
|
154
|
+
const includeStaticPaths = Boolean(args["static-paths"]) || hasDynamicSegments(routePath) && (render === "ssg" || render === "isg");
|
|
155
|
+
const title = args.title ?? titleFromPath(routePath);
|
|
156
|
+
if (project.mode === "pages") {
|
|
157
|
+
if (args.shell) throw new Error("`pracht generate route --shell` is only available for manifest apps.");
|
|
158
|
+
if (middleware.length > 0) throw new Error("`pracht generate route --middleware` is only available for manifest apps.");
|
|
159
|
+
return generatePagesRoute({
|
|
160
|
+
includeErrorBoundary,
|
|
161
|
+
includeLoader,
|
|
162
|
+
includeStaticPaths,
|
|
163
|
+
project,
|
|
164
|
+
render,
|
|
165
|
+
routePath,
|
|
166
|
+
title
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
170
|
+
assertFileExists(manifestPath, `App manifest not found at ${project.appFile}.`);
|
|
171
|
+
const manifestSource = readFileSync(manifestPath, "utf-8");
|
|
172
|
+
const registeredShells = new Set(extractRegistryEntries(manifestSource, "shells").map((entry) => entry.name));
|
|
173
|
+
const registeredMiddleware = new Set(extractRegistryEntries(manifestSource, "middleware").map((entry) => entry.name));
|
|
174
|
+
const shellName = args.shell;
|
|
175
|
+
if (shellName && !registeredShells.has(shellName)) throw new Error(`Shell "${shellName}" is not registered in ${project.appFile}.`);
|
|
176
|
+
for (const name of middleware) if (!registeredMiddleware.has(name)) throw new Error(`Middleware "${name}" is not registered in ${project.appFile}.`);
|
|
177
|
+
const routeFile = resolveRouteModulePath(project, routePath, ".tsx");
|
|
178
|
+
writeGeneratedFile(routeFile.absolutePath, buildManifestRouteModuleSource({
|
|
179
|
+
includeErrorBoundary,
|
|
180
|
+
includeLoader,
|
|
181
|
+
includeStaticPaths,
|
|
182
|
+
routePath,
|
|
183
|
+
title
|
|
184
|
+
}));
|
|
185
|
+
let nextManifestSource = ensureCoreNamedImport(manifestSource, "route");
|
|
186
|
+
if (render === "isg") nextManifestSource = ensureCoreNamedImport(nextManifestSource, "timeRevalidate");
|
|
187
|
+
const routeModulePath = toManifestModulePath(manifestPath, routeFile.absolutePath);
|
|
188
|
+
const meta = [`id: ${quote(routeIdFromPath(routePath))}`, `render: ${quote(render)}`];
|
|
189
|
+
if (shellName) meta.push(`shell: ${quote(shellName)}`);
|
|
190
|
+
if (middleware.length > 0) meta.push(`middleware: [${middleware.map((item) => quote(item)).join(", ")}]`);
|
|
191
|
+
if (render === "isg") {
|
|
192
|
+
const seconds = requirePositiveInteger(args.revalidate, "revalidate", 3600);
|
|
193
|
+
meta.push(`revalidate: timeRevalidate(${seconds})`);
|
|
194
|
+
}
|
|
195
|
+
nextManifestSource = insertArrayItem(nextManifestSource, "routes", [
|
|
196
|
+
`route(${quote(routePath)}, ${quote(routeModulePath)}, {`,
|
|
197
|
+
...meta.map((line) => ` ${line},`),
|
|
198
|
+
"})"
|
|
199
|
+
].join("\n"));
|
|
200
|
+
writeFileSync(manifestPath, ensureTrailingNewline(nextManifestSource), "utf-8");
|
|
201
|
+
return {
|
|
202
|
+
created: [displayPath(project.root, routeFile.absolutePath)],
|
|
203
|
+
kind: "route",
|
|
204
|
+
updated: [displayPath(project.root, manifestPath)]
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function generatePagesRoute({ includeErrorBoundary, includeLoader, includeStaticPaths, project, render, routePath, title }) {
|
|
208
|
+
const routeFile = resolvePagesRouteModulePath(project, routePath, ".tsx");
|
|
209
|
+
writeGeneratedFile(routeFile.absolutePath, buildPagesRouteModuleSource({
|
|
210
|
+
includeErrorBoundary,
|
|
211
|
+
includeLoader,
|
|
212
|
+
includeStaticPaths,
|
|
213
|
+
render,
|
|
214
|
+
routePath,
|
|
215
|
+
title
|
|
216
|
+
}));
|
|
217
|
+
return {
|
|
218
|
+
created: [displayPath(project.root, routeFile.absolutePath)],
|
|
219
|
+
kind: "route",
|
|
220
|
+
updated: []
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function generateShell(name, project) {
|
|
224
|
+
if (project.mode === "pages") throw new Error("Pages router apps use a single `_app` shell. `pracht generate shell` is only available for manifest apps.");
|
|
225
|
+
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
226
|
+
assertFileExists(manifestPath, `App manifest not found at ${project.appFile}.`);
|
|
227
|
+
const shellFile = resolveScopedFile(project.root, project.shellsDir, `${name}.tsx`);
|
|
228
|
+
writeGeneratedFile(shellFile, buildShellModuleSource(name));
|
|
229
|
+
writeFileSync(manifestPath, ensureTrailingNewline(upsertObjectEntry(readFileSync(manifestPath, "utf-8"), "shells", `${name}: ${quote(toManifestModulePath(manifestPath, shellFile))}`)), "utf-8");
|
|
230
|
+
return {
|
|
231
|
+
created: [displayPath(project.root, shellFile)],
|
|
232
|
+
kind: "shell",
|
|
233
|
+
updated: [displayPath(project.root, manifestPath)]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function generateMiddleware(name, project) {
|
|
237
|
+
if (project.mode === "pages") throw new Error("Pages router apps do not use manifest middleware registration. `pracht generate middleware` is only available for manifest apps.");
|
|
238
|
+
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
239
|
+
assertFileExists(manifestPath, `App manifest not found at ${project.appFile}.`);
|
|
240
|
+
const middlewareFile = resolveScopedFile(project.root, project.middlewareDir, `${name}.ts`);
|
|
241
|
+
writeGeneratedFile(middlewareFile, buildMiddlewareModuleSource());
|
|
242
|
+
writeFileSync(manifestPath, ensureTrailingNewline(upsertObjectEntry(readFileSync(manifestPath, "utf-8"), "middleware", `${name}: ${quote(toManifestModulePath(manifestPath, middlewareFile))}`)), "utf-8");
|
|
243
|
+
return {
|
|
244
|
+
created: [displayPath(project.root, middlewareFile)],
|
|
245
|
+
kind: "middleware",
|
|
246
|
+
updated: [displayPath(project.root, manifestPath)]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function generateApi(args, project) {
|
|
250
|
+
const endpointPath = normalizeApiPath(args.path);
|
|
251
|
+
const methods = parseApiMethods(args.methods);
|
|
252
|
+
const apiFile = resolveApiModulePath(project, endpointPath);
|
|
253
|
+
writeGeneratedFile(apiFile.absolutePath, buildApiRouteSource({
|
|
254
|
+
endpointPath,
|
|
255
|
+
methods
|
|
256
|
+
}));
|
|
257
|
+
return {
|
|
258
|
+
created: [displayPath(project.root, apiFile.absolutePath)],
|
|
259
|
+
kind: "api",
|
|
260
|
+
updated: []
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function buildManifestRouteModuleSource({ includeErrorBoundary, includeLoader, includeStaticPaths, routePath, title }) {
|
|
264
|
+
const params = dynamicParamNames(routePath);
|
|
265
|
+
const imports = [];
|
|
266
|
+
const sections = [];
|
|
267
|
+
if (includeLoader) imports.push("LoaderArgs", "RouteComponentProps");
|
|
268
|
+
if (includeErrorBoundary) imports.push("ErrorBoundaryProps");
|
|
269
|
+
if (imports.length > 0) {
|
|
270
|
+
sections.push(`import type { ${imports.join(", ")} } from "@pracht/core";`);
|
|
271
|
+
sections.push("");
|
|
272
|
+
}
|
|
273
|
+
if (includeLoader) sections.push("export async function loader(_args: LoaderArgs) {", ` return { message: ${quote(`Welcome to ${title}.`)} };`, "}", "");
|
|
274
|
+
if (includeStaticPaths) sections.push("export function getStaticPaths() {", ` return [${buildStaticPathsStub(params)}];`, "}", "");
|
|
275
|
+
sections.push("export function head() {", ` return { title: ${quote(title)} };`, "}", "");
|
|
276
|
+
if (includeLoader) sections.push("export function Component({ data }: RouteComponentProps<typeof loader>) {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " <p>{data.message}</p>", " </section>", " );", "}");
|
|
277
|
+
else sections.push("export function Component() {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " </section>", " );", "}");
|
|
278
|
+
if (includeErrorBoundary) sections.push("", "export function ErrorBoundary({ error }: ErrorBoundaryProps) {", " return <p>{error.message}</p>;", "}");
|
|
279
|
+
return `${sections.join("\n")}\n`;
|
|
280
|
+
}
|
|
281
|
+
function buildPagesRouteModuleSource({ includeErrorBoundary, includeLoader, includeStaticPaths, render, routePath, title }) {
|
|
282
|
+
const params = dynamicParamNames(routePath);
|
|
283
|
+
const imports = [];
|
|
284
|
+
const sections = [];
|
|
285
|
+
if (includeLoader) imports.push("LoaderArgs", "RouteComponentProps");
|
|
286
|
+
if (includeErrorBoundary) imports.push("ErrorBoundaryProps");
|
|
287
|
+
if (imports.length > 0) {
|
|
288
|
+
sections.push(`import type { ${imports.join(", ")} } from "@pracht/core";`);
|
|
289
|
+
sections.push("");
|
|
290
|
+
}
|
|
291
|
+
sections.push(`export const RENDER_MODE = ${quote(render)};`, "");
|
|
292
|
+
if (includeLoader) sections.push("export async function loader(_args: LoaderArgs) {", ` return { message: ${quote(`Welcome to ${title}.`)} };`, "}", "");
|
|
293
|
+
if (includeStaticPaths) sections.push("export function getStaticPaths() {", ` return [${buildStaticPathsStub(params)}];`, "}", "");
|
|
294
|
+
if (includeLoader) sections.push("export function Component({ data }: RouteComponentProps<typeof loader>) {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " <p>{data.message}</p>", " </section>", " );", "}");
|
|
295
|
+
else sections.push("export function Component() {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " </section>", " );", "}");
|
|
296
|
+
if (includeErrorBoundary) sections.push("", "export function ErrorBoundary({ error }: ErrorBoundaryProps) {", " return <p>{error.message}</p>;", "}");
|
|
297
|
+
return `${sections.join("\n")}\n`;
|
|
298
|
+
}
|
|
299
|
+
function buildShellModuleSource(name) {
|
|
300
|
+
const title = titleCase(name);
|
|
301
|
+
return [
|
|
302
|
+
"import type { ShellProps } from \"@pracht/core\";",
|
|
303
|
+
"",
|
|
304
|
+
"export function Shell({ children }: ShellProps) {",
|
|
305
|
+
" return (",
|
|
306
|
+
` <div class=${quote(`${name}-shell`)}>`,
|
|
307
|
+
" <main>{children}</main>",
|
|
308
|
+
" </div>",
|
|
309
|
+
" );",
|
|
310
|
+
"}",
|
|
311
|
+
"",
|
|
312
|
+
"export function head() {",
|
|
313
|
+
` return { title: ${quote(title)} };`,
|
|
314
|
+
"}",
|
|
315
|
+
""
|
|
316
|
+
].join("\n");
|
|
317
|
+
}
|
|
318
|
+
function buildMiddlewareModuleSource() {
|
|
319
|
+
return [
|
|
320
|
+
"import type { MiddlewareFn } from \"@pracht/core\";",
|
|
321
|
+
"",
|
|
322
|
+
"export const middleware: MiddlewareFn = async (_args) => {",
|
|
323
|
+
" return;",
|
|
324
|
+
"};",
|
|
325
|
+
""
|
|
326
|
+
].join("\n");
|
|
327
|
+
}
|
|
328
|
+
function buildApiRouteSource({ endpointPath, methods }) {
|
|
329
|
+
return [
|
|
330
|
+
"import type { BaseRouteArgs } from \"@pracht/core\";",
|
|
331
|
+
"",
|
|
332
|
+
...methods.flatMap((method, index) => {
|
|
333
|
+
const lines = buildApiMethodSource(method, methods, endpointPath);
|
|
334
|
+
if (index === methods.length - 1) return lines;
|
|
335
|
+
return [...lines, ""];
|
|
336
|
+
}),
|
|
337
|
+
""
|
|
338
|
+
].join("\n");
|
|
339
|
+
}
|
|
340
|
+
function buildApiMethodSource(method, methods, endpointPath) {
|
|
341
|
+
if (method === "DELETE" || method === "HEAD") return [
|
|
342
|
+
`export function ${method}(_args: BaseRouteArgs) {`,
|
|
343
|
+
" return new Response(null, { status: 204 });",
|
|
344
|
+
"}"
|
|
345
|
+
];
|
|
346
|
+
if (method === "OPTIONS") return [
|
|
347
|
+
`export function ${method}(_args: BaseRouteArgs) {`,
|
|
348
|
+
" return new Response(null, {",
|
|
349
|
+
` headers: { allow: ${quote(methods.join(", "))} },`,
|
|
350
|
+
" status: 204,",
|
|
351
|
+
" });",
|
|
352
|
+
"}"
|
|
353
|
+
];
|
|
354
|
+
if (method === "GET") return [
|
|
355
|
+
`export function ${method}(_args: BaseRouteArgs) {`,
|
|
356
|
+
` return Response.json({ endpoint: ${quote(`/api${endpointPath}`)}, ok: true });`,
|
|
357
|
+
"}"
|
|
358
|
+
];
|
|
359
|
+
const status = method === "POST" ? 201 : 200;
|
|
360
|
+
return [
|
|
361
|
+
`export async function ${method}({ request }: BaseRouteArgs) {`,
|
|
362
|
+
" const body = await request.json();",
|
|
363
|
+
` return Response.json({ body, ok: true }, { status: ${status} });`,
|
|
364
|
+
"}"
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
function normalizeRoutePathString(value) {
|
|
368
|
+
if (!value || value === "/") return "/";
|
|
369
|
+
const normalized = `/${value}`.replace(/\/+/g, "/");
|
|
370
|
+
return normalized !== "/" && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
371
|
+
}
|
|
372
|
+
function normalizeApiPath(value) {
|
|
373
|
+
return normalizeRoutePathString(value).replace(/^\/api(?=\/|$)/, "") || "/";
|
|
374
|
+
}
|
|
375
|
+
function hasDynamicSegments(routePath) {
|
|
376
|
+
return routePath.split("/").some((segment) => segment.startsWith(":") || segment === "*");
|
|
377
|
+
}
|
|
378
|
+
function dynamicParamNames(routePath) {
|
|
379
|
+
return routePath.split("/").filter(Boolean).map((segment) => {
|
|
380
|
+
if (segment.startsWith(":")) return segment.slice(1);
|
|
381
|
+
if (segment === "*") return "slug";
|
|
382
|
+
return null;
|
|
383
|
+
}).filter((s) => s !== null);
|
|
384
|
+
}
|
|
385
|
+
function routeIdFromPath(routePath) {
|
|
386
|
+
if (routePath === "/") return "index";
|
|
387
|
+
return routePath.split("/").filter(Boolean).map((segment) => segment.replace(/^:/, "").replace(/\*/g, "splat")).join("-");
|
|
388
|
+
}
|
|
389
|
+
function titleFromPath(routePath) {
|
|
390
|
+
if (routePath === "/") return "Home";
|
|
391
|
+
return titleCase((routePath.split("/").filter(Boolean).at(-1) ?? "Page").replace(/^:/, "").replace(/\*/g, "slug"));
|
|
392
|
+
}
|
|
393
|
+
function titleCase(value) {
|
|
394
|
+
return value.split(/[-_/]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
|
|
395
|
+
}
|
|
396
|
+
function buildStaticPathsStub(params) {
|
|
397
|
+
if (params.length === 0) return "{}";
|
|
398
|
+
return `{ ${params.map((name) => `${name}: ${quote(`example-${name}`)}`).join(", ")} }`;
|
|
399
|
+
}
|
|
400
|
+
function escapeJsxText(value) {
|
|
401
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
export { generate_default as default };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineCommand, runMain } from "citty";
|
|
2
|
+
//#region src/constants.ts
|
|
3
|
+
const VERSION = "0.0.0";
|
|
4
|
+
const PROJECT_DEFAULTS = {
|
|
5
|
+
apiDir: "/src/api",
|
|
6
|
+
appFile: "/src/routes.ts",
|
|
7
|
+
middlewareDir: "/src/middleware",
|
|
8
|
+
pagesDefaultRender: "ssr",
|
|
9
|
+
pagesDir: "",
|
|
10
|
+
routesDir: "/src/routes",
|
|
11
|
+
serverDir: "/src/server",
|
|
12
|
+
shellsDir: "/src/shells"
|
|
13
|
+
};
|
|
14
|
+
const HTTP_METHODS = new Set([
|
|
15
|
+
"GET",
|
|
16
|
+
"POST",
|
|
17
|
+
"PUT",
|
|
18
|
+
"PATCH",
|
|
19
|
+
"DELETE",
|
|
20
|
+
"HEAD",
|
|
21
|
+
"OPTIONS"
|
|
22
|
+
]);
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/index.ts
|
|
25
|
+
runMain(defineCommand({
|
|
26
|
+
meta: {
|
|
27
|
+
name: "pracht",
|
|
28
|
+
version: VERSION,
|
|
29
|
+
description: "The pracht CLI"
|
|
30
|
+
},
|
|
31
|
+
subCommands: {
|
|
32
|
+
build: () => import("./build-BzQAQjuy.mjs").then((m) => m.default),
|
|
33
|
+
dev: () => import("./dev-BCFe3g38.mjs").then((m) => m.default),
|
|
34
|
+
doctor: () => import("./doctor-BcoqyBk-.mjs").then((m) => m.default),
|
|
35
|
+
generate: () => import("./generate-BXkePCIx.mjs").then((m) => m.default),
|
|
36
|
+
inspect: () => import("./inspect-DIjjCfs3.mjs").then((m) => m.default),
|
|
37
|
+
verify: () => import("./verify-CObGxWtW.mjs").then((m) => m.default)
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
//#endregion
|
|
41
|
+
export { PROJECT_DEFAULTS as n, VERSION as r, HTTP_METHODS as t };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { t as HTTP_METHODS } from "./index.mjs";
|
|
2
|
+
import { t as readClientBuildAssets } from "./build-metadata-IABPFFKk.mjs";
|
|
3
|
+
import { a as readProjectConfig, c as resolveProjectPath, p as handleCliError } from "./project-DydjqBoS.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { createServer } from "vite";
|
|
8
|
+
//#region src/commands/inspect.ts
|
|
9
|
+
const INSPECT_TARGETS = new Set([
|
|
10
|
+
"routes",
|
|
11
|
+
"api",
|
|
12
|
+
"build",
|
|
13
|
+
"all"
|
|
14
|
+
]);
|
|
15
|
+
const METHOD_ORDER = [...HTTP_METHODS];
|
|
16
|
+
var inspect_default = defineCommand({
|
|
17
|
+
meta: {
|
|
18
|
+
name: "inspect",
|
|
19
|
+
description: "Inspect resolved app graph"
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
target: {
|
|
23
|
+
type: "positional",
|
|
24
|
+
description: "Inspect target: routes, api, build, or all",
|
|
25
|
+
required: false
|
|
26
|
+
},
|
|
27
|
+
json: {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
description: "Output as JSON"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
async run({ args }) {
|
|
33
|
+
const target = args.target || "all";
|
|
34
|
+
if (!INSPECT_TARGETS.has(target)) handleCliError(/* @__PURE__ */ new Error(`Unknown inspect target: ${target}`), { json: Boolean(args.json) });
|
|
35
|
+
const report = await runInspect(process.cwd(), { target });
|
|
36
|
+
if (args.json) {
|
|
37
|
+
console.log(JSON.stringify(report, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
printInspectReport(report);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
async function runInspect(root, { target = "all" } = {}) {
|
|
44
|
+
const project = readProjectConfig(root);
|
|
45
|
+
if (!project.configFile) throw new Error("Missing vite config. `pracht inspect` requires a project with pracht configured.");
|
|
46
|
+
if (!project.hasPrachtPlugin) throw new Error("vite.config does not appear to register the pracht plugin.");
|
|
47
|
+
if (project.mode === "manifest") {
|
|
48
|
+
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
49
|
+
try {
|
|
50
|
+
readFileSync(manifestPath, "utf-8");
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error(`App manifest is missing at ${project.appFile}.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const server = await createServer({
|
|
56
|
+
root,
|
|
57
|
+
logLevel: "silent",
|
|
58
|
+
server: { middlewareMode: true }
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
const serverModule = await server.ssrLoadModule("virtual:pracht/server");
|
|
62
|
+
const report = { mode: project.mode };
|
|
63
|
+
if (target === "routes" || target === "all") report.routes = serializeRoutes(serverModule.resolvedApp.routes);
|
|
64
|
+
if (target === "api" || target === "all") report.api = await Promise.all(serverModule.apiRoutes.map(async (route) => ({
|
|
65
|
+
file: route.file,
|
|
66
|
+
methods: await detectApiMethods(server, root, route.file),
|
|
67
|
+
path: route.path
|
|
68
|
+
})));
|
|
69
|
+
if (target === "build" || target === "all") {
|
|
70
|
+
const buildAssets = readClientBuildAssets(root);
|
|
71
|
+
report.build = {
|
|
72
|
+
adapterTarget: serverModule.buildTarget,
|
|
73
|
+
clientEntryUrl: buildAssets.clientEntryUrl,
|
|
74
|
+
cssManifest: buildAssets.cssManifest,
|
|
75
|
+
jsManifest: buildAssets.jsManifest
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return report;
|
|
79
|
+
} finally {
|
|
80
|
+
await server.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function serializeRoutes(routes) {
|
|
84
|
+
return routes.map((route) => ({
|
|
85
|
+
file: route.file,
|
|
86
|
+
id: route.id,
|
|
87
|
+
loaderFile: route.loaderFile ?? null,
|
|
88
|
+
middleware: route.middleware,
|
|
89
|
+
path: route.path,
|
|
90
|
+
render: route.render ?? null,
|
|
91
|
+
revalidate: route.revalidate ?? null,
|
|
92
|
+
shell: route.shell ?? null,
|
|
93
|
+
shellFile: route.shellFile ?? null
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
async function detectApiMethods(server, root, file) {
|
|
97
|
+
const source = readFileSync(resolve(root, `.${file}`), "utf-8");
|
|
98
|
+
try {
|
|
99
|
+
const module = await server.ssrLoadModule(file);
|
|
100
|
+
return METHOD_ORDER.filter((method) => typeof module[method] === "function");
|
|
101
|
+
} catch {
|
|
102
|
+
return METHOD_ORDER.filter((method) => new RegExp(`export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+${method}\\b`).test(source));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function printInspectReport(report) {
|
|
106
|
+
console.log(`Pracht inspect (${report.mode} mode)`);
|
|
107
|
+
if (report.routes) {
|
|
108
|
+
console.log("\nRoutes");
|
|
109
|
+
for (const route of report.routes) console.log(` ${route.path} id=${route.id} render=${route.render ?? "n/a"} file=${route.file}`);
|
|
110
|
+
}
|
|
111
|
+
if (report.api) {
|
|
112
|
+
console.log("\nAPI");
|
|
113
|
+
if (report.api.length === 0) console.log(" No API routes found.");
|
|
114
|
+
else for (const route of report.api) {
|
|
115
|
+
const methods = route.methods.length > 0 ? route.methods.join(",") : "none";
|
|
116
|
+
console.log(` ${route.path} methods=${methods} file=${route.file}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (report.build) {
|
|
120
|
+
console.log("\nBuild");
|
|
121
|
+
console.log(` adapterTarget=${report.build.adapterTarget}`);
|
|
122
|
+
console.log(` clientEntryUrl=${report.build.clientEntryUrl ?? "null"}`);
|
|
123
|
+
console.log(` cssManifestKeys=${Object.keys(report.build.cssManifest).length}`);
|
|
124
|
+
console.log(` jsManifestKeys=${Object.keys(report.build.jsManifest).length}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { inspect_default as default };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//#region src/manifest.ts
|
|
2
|
+
function ensureCoreNamedImport(source, name) {
|
|
3
|
+
const match = source.match(/import\s*\{([^}]+)\}\s*from\s*["']@pracht\/core["'];?/);
|
|
4
|
+
if (!match) return `import { ${name} } from "@pracht/core";\n${source}`;
|
|
5
|
+
const names = match[1].split(",").map((item) => item.trim()).filter(Boolean);
|
|
6
|
+
if (!names.includes(name)) names.push(name);
|
|
7
|
+
return source.replace(match[0], `import { ${names.join(", ")} } from "@pracht/core";`);
|
|
8
|
+
}
|
|
9
|
+
function upsertObjectEntry(source, key, entry) {
|
|
10
|
+
const property = findNamedBlock(source, key, "{", "}");
|
|
11
|
+
if (!property) {
|
|
12
|
+
const routesMatch = source.match(/^(\s*)routes\s*:/m);
|
|
13
|
+
if (!routesMatch || routesMatch.index == null) throw new Error(`Could not find a "${key}" or "routes" block in the app manifest.`);
|
|
14
|
+
const indent = routesMatch[1];
|
|
15
|
+
const block = `${indent}${key}: {\n${indent} ${entry},\n${indent}},\n`;
|
|
16
|
+
return `${source.slice(0, routesMatch.index)}${block}${source.slice(routesMatch.index)}`;
|
|
17
|
+
}
|
|
18
|
+
return insertBlockEntry(source, property, entry);
|
|
19
|
+
}
|
|
20
|
+
function insertArrayItem(source, key, item) {
|
|
21
|
+
const property = findNamedBlock(source, key, "[", "]");
|
|
22
|
+
if (!property) throw new Error(`Could not find "${key}" in the app manifest.`);
|
|
23
|
+
return insertBlockEntry(source, property, item);
|
|
24
|
+
}
|
|
25
|
+
function toManifestModulePath(manifestPath, targetFilePath) {
|
|
26
|
+
const relativePath = targetFilePath.replaceAll("\\", "/").replace(manifestPath.replaceAll("\\", "/").replace(/\/[^/]+$/, ""), "").replace(/^\//, "");
|
|
27
|
+
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
28
|
+
}
|
|
29
|
+
function extractRegistryEntries(source, key) {
|
|
30
|
+
const block = findNamedBlock(source, key, "{", "}");
|
|
31
|
+
if (!block) return [];
|
|
32
|
+
const inner = source.slice(block.openIndex + 1, block.closeIndex);
|
|
33
|
+
const entries = [];
|
|
34
|
+
for (const match of inner.matchAll(/([A-Za-z0-9_-]+)\s*:\s*(?:(["'`])([^"'`]+)\2|\(\)\s*=>\s*import\(\s*(["'`])([^"'`]+)\4\s*\))/g)) entries.push({
|
|
35
|
+
name: match[1],
|
|
36
|
+
path: match[3] ?? match[5]
|
|
37
|
+
});
|
|
38
|
+
return entries;
|
|
39
|
+
}
|
|
40
|
+
function extractRelativeModulePaths(source) {
|
|
41
|
+
const results = /* @__PURE__ */ new Set();
|
|
42
|
+
for (const match of source.matchAll(/["'`]((?:\.\.\/|\.\/)[^"'`]+)["'`]/g)) results.add(match[1]);
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
function insertBlockEntry(source, block, entry) {
|
|
46
|
+
const inner = source.slice(block.openIndex + 1, block.closeIndex);
|
|
47
|
+
const closingIndent = block.indent;
|
|
48
|
+
const childIndent = `${closingIndent} `;
|
|
49
|
+
const trimmed = inner.trim();
|
|
50
|
+
if (!trimmed) return `${source.slice(0, block.openIndex + 1)}\n${indentMultiline(entry, childIndent)}\n${closingIndent}${source.slice(block.closeIndex)}`;
|
|
51
|
+
const insertPrefix = !/[,[{(]\s*$/.test(inner) && !/,\s*$/.test(trimmed) ? "," : "";
|
|
52
|
+
return `${source.slice(0, block.closeIndex)}${insertPrefix}\n${indentMultiline(entry, childIndent)}\n${closingIndent}${source.slice(block.closeIndex)}`;
|
|
53
|
+
}
|
|
54
|
+
function findNamedBlock(source, key, openChar, closeChar) {
|
|
55
|
+
const pattern = new RegExp(`^([ \\t]*)${key}\\s*:\\s*\\${openChar}`, "m");
|
|
56
|
+
const match = source.match(pattern);
|
|
57
|
+
if (!match || match.index == null) return null;
|
|
58
|
+
const openIndex = source.indexOf(openChar, match.index);
|
|
59
|
+
return {
|
|
60
|
+
closeIndex: findMatchingDelimiter(source, openIndex, openChar, closeChar),
|
|
61
|
+
indent: match[1],
|
|
62
|
+
openIndex
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function findMatchingDelimiter(source, openIndex, openChar, closeChar) {
|
|
66
|
+
let depth = 0;
|
|
67
|
+
let quoteChar = null;
|
|
68
|
+
let escaping = false;
|
|
69
|
+
for (let index = openIndex; index < source.length; index += 1) {
|
|
70
|
+
const current = source[index];
|
|
71
|
+
if (quoteChar) {
|
|
72
|
+
if (escaping) {
|
|
73
|
+
escaping = false;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (current === "\\") {
|
|
77
|
+
escaping = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (current === quoteChar) quoteChar = null;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (current === "\"" || current === "'" || current === "`") {
|
|
84
|
+
quoteChar = current;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (current === openChar) depth += 1;
|
|
88
|
+
if (current === closeChar) {
|
|
89
|
+
depth -= 1;
|
|
90
|
+
if (depth === 0) return index;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`Could not find matching ${closeChar} for ${openChar}.`);
|
|
94
|
+
}
|
|
95
|
+
function indentMultiline(value, indent) {
|
|
96
|
+
return value.split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { toManifestModulePath as a, insertArrayItem as i, extractRegistryEntries as n, upsertObjectEntry as o, extractRelativeModulePaths as r, ensureCoreNamedImport as t };
|