@pracht/cli 1.0.0 → 1.1.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 +18 -0
- package/README.md +11 -0
- package/bin/pracht.js +2 -0
- package/lib/build-metadata.js +73 -0
- package/lib/cli.js +8 -0
- package/lib/commands/build.js +4 -43
- package/lib/commands/inspect.js +163 -0
- package/package.json +2 -2
- package/test/pracht-cli.test.js +219 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @pracht/cli
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#70](https://github.com/JoviDeCroock/pracht/pull/70) [`ddd50a1`](https://github.com/JoviDeCroock/pracht/commit/ddd50a1edf82a6884881a91ce7172d87ec571cde) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - Add `pracht inspect` as a machine-readable app graph command.
|
|
8
|
+
|
|
9
|
+
The CLI can now emit resolved routes, API handlers, and build metadata via:
|
|
10
|
+
|
|
11
|
+
- `pracht inspect routes --json`
|
|
12
|
+
- `pracht inspect api --json`
|
|
13
|
+
- `pracht inspect build --json`
|
|
14
|
+
- `pracht inspect --json`
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [[`0d33c3d`](https://github.com/JoviDeCroock/pracht/commit/0d33c3dee00bf3940dc56bef3a171249a3d73e21), [`ba1eaea`](https://github.com/JoviDeCroock/pracht/commit/ba1eaeaf68ab63b47b08411fbdafae2fd98e5f09)]:
|
|
19
|
+
- @pracht/core@0.2.0
|
|
20
|
+
|
|
3
21
|
## 1.0.0
|
|
4
22
|
|
|
5
23
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -68,6 +68,17 @@ Create an API route under `src/api/`.
|
|
|
68
68
|
pracht generate api --path /health --methods GET,POST
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
### `pracht inspect`
|
|
72
|
+
|
|
73
|
+
Inspect the resolved app graph. Use `--json` for agent/tool consumption.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pracht inspect --json
|
|
77
|
+
pracht inspect routes --json
|
|
78
|
+
pracht inspect api --json
|
|
79
|
+
pracht inspect build --json
|
|
80
|
+
```
|
|
81
|
+
|
|
71
82
|
### `pracht doctor`
|
|
72
83
|
|
|
73
84
|
Validate the local app wiring across the whole project. Use `--json` for
|
package/bin/pracht.js
CHANGED
|
@@ -5,6 +5,7 @@ import { buildCommand } from "../lib/commands/build.js";
|
|
|
5
5
|
import { devCommand } from "../lib/commands/dev.js";
|
|
6
6
|
import { generateCommand } from "../lib/commands/generate.js";
|
|
7
7
|
import { verifyCommand } from "../lib/commands/verify.js";
|
|
8
|
+
import { inspectCommand } from "../lib/commands/inspect.js";
|
|
8
9
|
import { handleCliError, printHelp } from "../lib/cli.js";
|
|
9
10
|
import { VERSION } from "../lib/constants.js";
|
|
10
11
|
|
|
@@ -28,6 +29,7 @@ const handlers = {
|
|
|
28
29
|
doctor: doctorCommand,
|
|
29
30
|
generate: generateCommand,
|
|
30
31
|
verify: verifyCommand,
|
|
32
|
+
inspect: inspectCommand,
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
if (!(command in handlers)) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
const MANIFEST_PATHS = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"];
|
|
5
|
+
|
|
6
|
+
export function readClientBuildAssets(root = process.cwd()) {
|
|
7
|
+
const manifestPath = MANIFEST_PATHS.map((candidate) => resolve(root, candidate)).find((path) =>
|
|
8
|
+
existsSync(path),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
if (!manifestPath) {
|
|
12
|
+
return {
|
|
13
|
+
clientEntryUrl: null,
|
|
14
|
+
cssManifest: {},
|
|
15
|
+
jsManifest: {},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const rawManifest = readFileSync(manifestPath, "utf-8");
|
|
20
|
+
const manifest = JSON.parse(rawManifest);
|
|
21
|
+
const clientEntry = manifest["virtual:pracht/client"];
|
|
22
|
+
|
|
23
|
+
function collectTransitiveDeps(key) {
|
|
24
|
+
const css = new Set();
|
|
25
|
+
const js = new Set();
|
|
26
|
+
const visited = new Set();
|
|
27
|
+
|
|
28
|
+
function collect(currentKey) {
|
|
29
|
+
if (visited.has(currentKey)) return;
|
|
30
|
+
visited.add(currentKey);
|
|
31
|
+
|
|
32
|
+
const entry = manifest[currentKey];
|
|
33
|
+
if (!entry) return;
|
|
34
|
+
|
|
35
|
+
for (const cssFile of entry.css ?? []) {
|
|
36
|
+
css.add(cssFile);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
js.add(entry.file);
|
|
40
|
+
|
|
41
|
+
for (const importedKey of entry.imports ?? []) {
|
|
42
|
+
collect(importedKey);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
collect(key);
|
|
47
|
+
return {
|
|
48
|
+
css: [...css],
|
|
49
|
+
js: [...js],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const cssManifest = {};
|
|
54
|
+
const jsManifest = {};
|
|
55
|
+
|
|
56
|
+
for (const [key, entry] of Object.entries(manifest)) {
|
|
57
|
+
if (!entry.src) continue;
|
|
58
|
+
|
|
59
|
+
const deps = collectTransitiveDeps(key);
|
|
60
|
+
if (deps.css.length > 0) {
|
|
61
|
+
cssManifest[key] = deps.css.map((file) => `/${file}`);
|
|
62
|
+
}
|
|
63
|
+
if (deps.js.length > 0) {
|
|
64
|
+
jsManifest[key] = deps.js.map((file) => `/${file}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
|
|
70
|
+
cssManifest,
|
|
71
|
+
jsManifest,
|
|
72
|
+
};
|
|
73
|
+
}
|
package/lib/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ Usage:
|
|
|
8
8
|
pracht build Production build (client + server)
|
|
9
9
|
pracht generate <kind> [flags] Scaffold framework files
|
|
10
10
|
pracht verify [--changed] [--json] Fast framework-aware verification
|
|
11
|
+
pracht inspect [target] [--json] Inspect resolved app graph
|
|
11
12
|
pracht doctor [--json] Validate app wiring
|
|
12
13
|
|
|
13
14
|
Generate kinds:
|
|
@@ -18,6 +19,13 @@ Generate kinds:
|
|
|
18
19
|
`);
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export function printInspectHelp() {
|
|
23
|
+
console.log(`Usage:
|
|
24
|
+
pracht inspect [routes|api|build] [--json]
|
|
25
|
+
pracht inspect --json
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export function printGenerateHelp() {
|
|
22
30
|
console.log(`Usage:
|
|
23
31
|
pracht generate route --path /dashboard [--render ssr|spa|ssg|isg] [--shell app] [--middleware auth] [--loader] [--json]
|
package/lib/commands/build.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cpSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
readdirSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
} from "node:fs";
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
10
2
|
import { dirname, join, resolve } from "node:path";
|
|
11
3
|
|
|
12
4
|
import { build as viteBuild } from "vite";
|
|
13
5
|
|
|
6
|
+
import { readClientBuildAssets } from "../build-metadata.js";
|
|
14
7
|
import { writeVercelBuildOutput } from "../build-shared.js";
|
|
15
8
|
|
|
16
9
|
export async function buildCommand() {
|
|
@@ -57,43 +50,11 @@ export async function buildCommand() {
|
|
|
57
50
|
if (existsSync(serverEntry)) {
|
|
58
51
|
const serverMod = await import(serverEntry);
|
|
59
52
|
const { prerenderApp } = serverMod;
|
|
60
|
-
const
|
|
61
|
-
const viteManifest = existsSync(manifestPath)
|
|
62
|
-
? JSON.parse(readFileSync(manifestPath, "utf-8"))
|
|
63
|
-
: {};
|
|
64
|
-
|
|
65
|
-
const clientEntry = viteManifest["virtual:pracht/client"];
|
|
66
|
-
const clientEntryUrl = clientEntry ? `/${clientEntry.file}` : undefined;
|
|
67
|
-
|
|
68
|
-
function collectTransitiveCss(key) {
|
|
69
|
-
const css = new Set();
|
|
70
|
-
const visited = new Set();
|
|
71
|
-
|
|
72
|
-
function collect(currentKey) {
|
|
73
|
-
if (visited.has(currentKey)) return;
|
|
74
|
-
visited.add(currentKey);
|
|
75
|
-
const entry = viteManifest[currentKey];
|
|
76
|
-
if (!entry) return;
|
|
77
|
-
for (const cssFile of entry.css ?? []) css.add(cssFile);
|
|
78
|
-
for (const importedKey of entry.imports ?? []) collect(importedKey);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
collect(key);
|
|
82
|
-
return [...css];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const cssManifest = {};
|
|
86
|
-
for (const [key, entry] of Object.entries(viteManifest)) {
|
|
87
|
-
if (!entry.src) continue;
|
|
88
|
-
const css = collectTransitiveCss(key);
|
|
89
|
-
if (css.length > 0) {
|
|
90
|
-
cssManifest[key] = css.map((file) => `/${file}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
53
|
+
const { clientEntryUrl, cssManifest } = readClientBuildAssets(root);
|
|
93
54
|
|
|
94
55
|
const { pages, isgManifest } = await prerenderApp({
|
|
95
56
|
app: serverMod.resolvedApp,
|
|
96
|
-
clientEntryUrl,
|
|
57
|
+
clientEntryUrl: clientEntryUrl ?? undefined,
|
|
97
58
|
cssManifest,
|
|
98
59
|
registry: serverMod.registry,
|
|
99
60
|
withISGManifest: true,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { createServer } from "vite";
|
|
5
|
+
|
|
6
|
+
import { handleCliError, parseFlags, printInspectHelp, requireOptionalString } from "../cli.js";
|
|
7
|
+
import { readClientBuildAssets } from "../build-metadata.js";
|
|
8
|
+
import { HTTP_METHODS } from "../constants.js";
|
|
9
|
+
import { readProjectConfig, resolveProjectPath } from "../project.js";
|
|
10
|
+
|
|
11
|
+
const INSPECT_TARGETS = new Set(["routes", "api", "build", "all"]);
|
|
12
|
+
const METHOD_ORDER = [...HTTP_METHODS];
|
|
13
|
+
|
|
14
|
+
export async function inspectCommand(args) {
|
|
15
|
+
const options = parseFlags(args);
|
|
16
|
+
const target = requireOptionalString(options, "target") ?? options._[0] ?? "all";
|
|
17
|
+
|
|
18
|
+
if (options.help || target === "help") {
|
|
19
|
+
printInspectHelp();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!INSPECT_TARGETS.has(target)) {
|
|
24
|
+
handleCliError(new Error(`Unknown inspect target: ${target}`), { json: !!options.json });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const report = await runInspect(process.cwd(), { target });
|
|
28
|
+
|
|
29
|
+
if (options.json) {
|
|
30
|
+
console.log(JSON.stringify(report, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
printInspectReport(report);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function runInspect(root, { target = "all" } = {}) {
|
|
38
|
+
const project = readProjectConfig(root);
|
|
39
|
+
|
|
40
|
+
if (!project.configFile) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Missing vite config. `pracht inspect` requires a project with pracht configured.",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!project.hasPrachtPlugin) {
|
|
47
|
+
throw new Error("vite.config does not appear to register the pracht plugin.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (project.mode === "manifest") {
|
|
51
|
+
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
52
|
+
try {
|
|
53
|
+
readFileSync(manifestPath, "utf-8");
|
|
54
|
+
} catch {
|
|
55
|
+
throw new Error(`App manifest is missing at ${project.appFile}.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const server = await createServer({
|
|
60
|
+
root,
|
|
61
|
+
logLevel: "silent",
|
|
62
|
+
server: {
|
|
63
|
+
middlewareMode: true,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const serverModule = await server.ssrLoadModule("virtual:pracht/server");
|
|
69
|
+
const report = {
|
|
70
|
+
mode: project.mode,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (target === "routes" || target === "all") {
|
|
74
|
+
report.routes = serializeRoutes(serverModule.resolvedApp.routes);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (target === "api" || target === "all") {
|
|
78
|
+
report.api = await Promise.all(
|
|
79
|
+
serverModule.apiRoutes.map(async (route) => ({
|
|
80
|
+
file: route.file,
|
|
81
|
+
methods: await detectApiMethods(server, root, route.file),
|
|
82
|
+
path: route.path,
|
|
83
|
+
})),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (target === "build" || target === "all") {
|
|
88
|
+
const buildAssets = readClientBuildAssets(root);
|
|
89
|
+
report.build = {
|
|
90
|
+
adapterTarget: serverModule.buildTarget,
|
|
91
|
+
clientEntryUrl: buildAssets.clientEntryUrl,
|
|
92
|
+
cssManifest: buildAssets.cssManifest,
|
|
93
|
+
jsManifest: buildAssets.jsManifest,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return report;
|
|
98
|
+
} finally {
|
|
99
|
+
await server.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function serializeRoutes(routes) {
|
|
104
|
+
return routes.map((route) => ({
|
|
105
|
+
file: route.file,
|
|
106
|
+
id: route.id,
|
|
107
|
+
loaderFile: route.loaderFile ?? null,
|
|
108
|
+
middleware: route.middleware,
|
|
109
|
+
path: route.path,
|
|
110
|
+
render: route.render ?? null,
|
|
111
|
+
revalidate: route.revalidate ?? null,
|
|
112
|
+
shell: route.shell ?? null,
|
|
113
|
+
shellFile: route.shellFile ?? null,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function detectApiMethods(server, root, file) {
|
|
118
|
+
const resolvedFile = resolve(root, `.${file}`);
|
|
119
|
+
const source = readFileSync(resolvedFile, "utf-8");
|
|
120
|
+
|
|
121
|
+
// Use module evaluation first so re-exported handlers are reflected too.
|
|
122
|
+
try {
|
|
123
|
+
const module = await server.ssrLoadModule(file);
|
|
124
|
+
return METHOD_ORDER.filter((method) => typeof module[method] === "function");
|
|
125
|
+
} catch {
|
|
126
|
+
return METHOD_ORDER.filter((method) =>
|
|
127
|
+
new RegExp(`export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+${method}\\b`).test(source),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function printInspectReport(report) {
|
|
133
|
+
console.log(`Pracht inspect (${report.mode} mode)`);
|
|
134
|
+
|
|
135
|
+
if (report.routes) {
|
|
136
|
+
console.log("\nRoutes");
|
|
137
|
+
for (const route of report.routes) {
|
|
138
|
+
console.log(
|
|
139
|
+
` ${route.path} id=${route.id} render=${route.render ?? "n/a"} file=${route.file}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (report.api) {
|
|
145
|
+
console.log("\nAPI");
|
|
146
|
+
if (report.api.length === 0) {
|
|
147
|
+
console.log(" No API routes found.");
|
|
148
|
+
} else {
|
|
149
|
+
for (const route of report.api) {
|
|
150
|
+
const methods = route.methods.length > 0 ? route.methods.join(",") : "none";
|
|
151
|
+
console.log(` ${route.path} methods=${methods} file=${route.file}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (report.build) {
|
|
157
|
+
console.log("\nBuild");
|
|
158
|
+
console.log(` adapterTarget=${report.build.adapterTarget}`);
|
|
159
|
+
console.log(` clientEntryUrl=${report.build.clientEntryUrl ?? "null"}`);
|
|
160
|
+
console.log(` cssManifestKeys=${Object.keys(report.build.cssManifest).length}`);
|
|
161
|
+
console.log(` jsManifestKeys=${Object.keys(report.build.jsManifest).length}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/cli",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/JoviDeCroock/pracht/issues"
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
"provenance": true
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@pracht/core": "0.
|
|
21
|
+
"@pracht/core": "0.2.0"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/test/pracht-cli.test.js
CHANGED
|
@@ -2,11 +2,16 @@ import { execFileSync, spawnSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
|
|
7
7
|
import { afterEach, describe, expect, it } from "vitest";
|
|
8
8
|
|
|
9
9
|
const cliPath = fileURLToPath(new URL("../bin/pracht.js", import.meta.url));
|
|
10
|
+
const repoRoot = resolve(dirname(cliPath), "../../..");
|
|
11
|
+
const repoTempRoot = resolve(dirname(cliPath), "../test/.tmp");
|
|
12
|
+
const coreImportPath = resolve(repoRoot, "packages/framework/src/index.ts");
|
|
13
|
+
const nodeAdapterImportPath = resolve(repoRoot, "packages/adapter-node/src/index.ts");
|
|
14
|
+
const vitePluginImportPath = resolve(repoRoot, "packages/vite-plugin/src/index.ts");
|
|
10
15
|
const tempDirs = [];
|
|
11
16
|
|
|
12
17
|
afterEach(() => {
|
|
@@ -266,6 +271,69 @@ export const app = defineApp({
|
|
|
266
271
|
).toBe(true);
|
|
267
272
|
});
|
|
268
273
|
|
|
274
|
+
it("inspects resolved routes, api handlers, and build metadata as JSON", () => {
|
|
275
|
+
const appDir = createRepoTempDir("pracht-cli-inspect-");
|
|
276
|
+
writeInspectableManifestApp(appDir);
|
|
277
|
+
|
|
278
|
+
const routes = JSON.parse(runCli(["inspect", "routes", "--json"], { cwd: appDir }).stdout);
|
|
279
|
+
const api = JSON.parse(runCli(["inspect", "api", "--json"], { cwd: appDir }).stdout);
|
|
280
|
+
const build = JSON.parse(runCli(["inspect", "build", "--json"], { cwd: appDir }).stdout);
|
|
281
|
+
const all = JSON.parse(runCli(["inspect", "--json"], { cwd: appDir }).stdout);
|
|
282
|
+
|
|
283
|
+
expect(routes).toEqual({
|
|
284
|
+
mode: "manifest",
|
|
285
|
+
routes: [
|
|
286
|
+
{
|
|
287
|
+
file: "./routes/dashboard.tsx",
|
|
288
|
+
id: "dashboard",
|
|
289
|
+
loaderFile: "./server/dashboard-loader.ts",
|
|
290
|
+
middleware: ["auth"],
|
|
291
|
+
path: "/dashboard",
|
|
292
|
+
render: "isg",
|
|
293
|
+
revalidate: {
|
|
294
|
+
kind: "time",
|
|
295
|
+
seconds: 60,
|
|
296
|
+
},
|
|
297
|
+
shell: "app",
|
|
298
|
+
shellFile: "./shells/app.tsx",
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(api).toEqual({
|
|
304
|
+
api: [
|
|
305
|
+
{
|
|
306
|
+
file: "/src/api/health.ts",
|
|
307
|
+
methods: ["GET", "POST"],
|
|
308
|
+
path: "/api/health",
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
mode: "manifest",
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(build).toEqual({
|
|
315
|
+
build: {
|
|
316
|
+
adapterTarget: "node",
|
|
317
|
+
clientEntryUrl: "/assets/client.js",
|
|
318
|
+
cssManifest: {
|
|
319
|
+
"src/routes/dashboard.tsx": ["/assets/dashboard.css"],
|
|
320
|
+
"src/shells/app.tsx": ["/assets/app.css"],
|
|
321
|
+
},
|
|
322
|
+
jsManifest: {
|
|
323
|
+
"src/routes/dashboard.tsx": ["/assets/dashboard.js", "/assets/vendor.js"],
|
|
324
|
+
"src/shells/app.tsx": ["/assets/app.js", "/assets/vendor.js"],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
mode: "manifest",
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(all).toEqual({
|
|
331
|
+
...routes,
|
|
332
|
+
...api,
|
|
333
|
+
...build,
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
269
337
|
it("scaffolds pages-router routes without touching a manifest", () => {
|
|
270
338
|
const appDir = createTempDir("pracht-cli-pages-");
|
|
271
339
|
writePagesApp(appDir);
|
|
@@ -290,6 +358,13 @@ function createTempDir(prefix) {
|
|
|
290
358
|
return dir;
|
|
291
359
|
}
|
|
292
360
|
|
|
361
|
+
function createRepoTempDir(prefix) {
|
|
362
|
+
mkdirSync(repoTempRoot, { recursive: true });
|
|
363
|
+
const dir = mkdtempSync(join(repoTempRoot, prefix));
|
|
364
|
+
tempDirs.push(dir);
|
|
365
|
+
return dir;
|
|
366
|
+
}
|
|
367
|
+
|
|
293
368
|
function runCli(args, { cwd }) {
|
|
294
369
|
return {
|
|
295
370
|
stdout: execFileSync(process.execPath, [cliPath, ...args], {
|
|
@@ -378,6 +453,149 @@ export const app = defineApp({
|
|
|
378
453
|
);
|
|
379
454
|
}
|
|
380
455
|
|
|
456
|
+
function writeInspectableManifestApp(appDir) {
|
|
457
|
+
const vitePluginImport = pathToFileURL(vitePluginImportPath).href;
|
|
458
|
+
|
|
459
|
+
writeProjectFile(
|
|
460
|
+
appDir,
|
|
461
|
+
"package.json",
|
|
462
|
+
JSON.stringify(
|
|
463
|
+
{
|
|
464
|
+
name: "fixture-inspect-app",
|
|
465
|
+
private: true,
|
|
466
|
+
type: "module",
|
|
467
|
+
},
|
|
468
|
+
null,
|
|
469
|
+
2,
|
|
470
|
+
),
|
|
471
|
+
);
|
|
472
|
+
writeProjectFile(
|
|
473
|
+
appDir,
|
|
474
|
+
"vite.config.ts",
|
|
475
|
+
`import { defineConfig } from "vite";
|
|
476
|
+
import { pracht } from ${JSON.stringify(vitePluginImport)};
|
|
477
|
+
|
|
478
|
+
export default defineConfig(async () => ({
|
|
479
|
+
plugins: [await pracht()],
|
|
480
|
+
resolve: {
|
|
481
|
+
alias: {
|
|
482
|
+
"@pracht/adapter-node": ${JSON.stringify(nodeAdapterImportPath)},
|
|
483
|
+
"@pracht/core": ${JSON.stringify(coreImportPath)},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
}));
|
|
487
|
+
`,
|
|
488
|
+
);
|
|
489
|
+
writeProjectFile(
|
|
490
|
+
appDir,
|
|
491
|
+
"src/routes.ts",
|
|
492
|
+
`import { defineApp, group, route, timeRevalidate } from "@pracht/core";
|
|
493
|
+
|
|
494
|
+
export const app = defineApp({
|
|
495
|
+
shells: {
|
|
496
|
+
app: () => import("./shells/app.tsx"),
|
|
497
|
+
},
|
|
498
|
+
middleware: {
|
|
499
|
+
auth: () => import("./middleware/auth.ts"),
|
|
500
|
+
},
|
|
501
|
+
routes: [
|
|
502
|
+
group({ shell: "app", middleware: ["auth"] }, [
|
|
503
|
+
route("/dashboard", {
|
|
504
|
+
component: () => import("./routes/dashboard.tsx"),
|
|
505
|
+
loader: () => import("./server/dashboard-loader.ts"),
|
|
506
|
+
render: "isg",
|
|
507
|
+
revalidate: timeRevalidate(60),
|
|
508
|
+
}),
|
|
509
|
+
]),
|
|
510
|
+
],
|
|
511
|
+
});
|
|
512
|
+
`,
|
|
513
|
+
);
|
|
514
|
+
writeProjectFile(
|
|
515
|
+
appDir,
|
|
516
|
+
"src/routes/dashboard.tsx",
|
|
517
|
+
`import type { RouteComponentProps } from "@pracht/core";
|
|
518
|
+
|
|
519
|
+
export function Component({ data }: RouteComponentProps) {
|
|
520
|
+
return <main>{JSON.stringify(data)}</main>;
|
|
521
|
+
}
|
|
522
|
+
`,
|
|
523
|
+
);
|
|
524
|
+
writeProjectFile(
|
|
525
|
+
appDir,
|
|
526
|
+
"src/server/dashboard-loader.ts",
|
|
527
|
+
`import type { LoaderArgs } from "@pracht/core";
|
|
528
|
+
|
|
529
|
+
export async function loader(_args: LoaderArgs) {
|
|
530
|
+
return { ok: true };
|
|
531
|
+
}
|
|
532
|
+
`,
|
|
533
|
+
);
|
|
534
|
+
writeProjectFile(
|
|
535
|
+
appDir,
|
|
536
|
+
"src/shells/app.tsx",
|
|
537
|
+
`import type { ShellProps } from "@pracht/core";
|
|
538
|
+
|
|
539
|
+
export function Shell({ children }: ShellProps) {
|
|
540
|
+
return <div>{children}</div>;
|
|
541
|
+
}
|
|
542
|
+
`,
|
|
543
|
+
);
|
|
544
|
+
writeProjectFile(
|
|
545
|
+
appDir,
|
|
546
|
+
"src/middleware/auth.ts",
|
|
547
|
+
`import type { MiddlewareFn } from "@pracht/core";
|
|
548
|
+
|
|
549
|
+
export const middleware: MiddlewareFn = async () => {
|
|
550
|
+
return;
|
|
551
|
+
};
|
|
552
|
+
`,
|
|
553
|
+
);
|
|
554
|
+
writeProjectFile(
|
|
555
|
+
appDir,
|
|
556
|
+
"src/api/health.ts",
|
|
557
|
+
`import type { BaseRouteArgs } from "@pracht/core";
|
|
558
|
+
|
|
559
|
+
export function GET(_args: BaseRouteArgs) {
|
|
560
|
+
return Response.json({ ok: true });
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export async function POST(_args: BaseRouteArgs) {
|
|
564
|
+
return Response.json({ created: true }, { status: 201 });
|
|
565
|
+
}
|
|
566
|
+
`,
|
|
567
|
+
);
|
|
568
|
+
writeProjectFile(
|
|
569
|
+
appDir,
|
|
570
|
+
"dist/client/.vite/manifest.json",
|
|
571
|
+
JSON.stringify(
|
|
572
|
+
{
|
|
573
|
+
"virtual:pracht/client": {
|
|
574
|
+
file: "assets/client.js",
|
|
575
|
+
imports: ["assets/vendor.js"],
|
|
576
|
+
},
|
|
577
|
+
"src/routes/dashboard.tsx": {
|
|
578
|
+
css: ["assets/dashboard.css"],
|
|
579
|
+
file: "assets/dashboard.js",
|
|
580
|
+
imports: ["assets/vendor.js"],
|
|
581
|
+
src: "src/routes/dashboard.tsx",
|
|
582
|
+
},
|
|
583
|
+
"src/shells/app.tsx": {
|
|
584
|
+
css: ["assets/app.css"],
|
|
585
|
+
file: "assets/app.js",
|
|
586
|
+
imports: ["assets/vendor.js"],
|
|
587
|
+
src: "src/shells/app.tsx",
|
|
588
|
+
},
|
|
589
|
+
"assets/vendor.js": {
|
|
590
|
+
file: "assets/vendor.js",
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
null,
|
|
594
|
+
2,
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
381
599
|
function writePagesApp(appDir) {
|
|
382
600
|
writeProjectFile(
|
|
383
601
|
appDir,
|