@pracht/cli 1.2.1 → 1.2.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/CHANGELOG.md +74 -0
- package/dist/{build-DlABFPbG.mjs → build-DkP2ffu9.mjs} +8 -0
- package/dist/{doctor-CQq64tfW.mjs → doctor-CwQJjJ83.mjs} +1 -1
- package/dist/{generate-sHtRHRAH.mjs → generate-C9AMSGsv.mjs} +141 -138
- package/dist/index.mjs +5 -5
- package/dist/{inspect-DP-h3vLw.mjs → inspect-MseHPU9s.mjs} +1 -1
- package/dist/{verification-CId4crSF.mjs → verification-Dwf1ZuNn.mjs} +116 -108
- package/dist/{verify-D7Bg1Su6.mjs → verify-o0G5Xuff.mjs} +1 -1
- package/package.json +9 -2
- package/src/build-metadata.ts +0 -101
- package/src/build-shared.ts +0 -163
- package/src/commands/build.ts +0 -137
- package/src/commands/dev.ts +0 -27
- package/src/commands/doctor.ts +0 -33
- package/src/commands/generate.ts +0 -616
- package/src/commands/inspect.ts +0 -212
- package/src/commands/verify.ts +0 -37
- package/src/constants.ts +0 -26
- package/src/index.ts +0 -21
- package/src/manifest.ts +0 -166
- package/src/project.ts +0 -160
- package/src/utils.ts +0 -72
- package/src/verification.ts +0 -710
- package/test/pracht-cli.test.js +0 -633
- package/tsdown.config.ts +0 -8
- /package/dist/{manifest-DGq1n5LT.mjs → manifest-Bs5hp3gA.mjs} +0 -0
- /package/dist/{project-DUyH_7eJ.mjs → project-BdMiN3s7.mjs} +0 -0
package/src/commands/inspect.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { defineCommand } from "citty";
|
|
5
|
-
import { createServer, type ViteDevServer } from "vite";
|
|
6
|
-
|
|
7
|
-
import { handleCliError } from "../utils.js";
|
|
8
|
-
import { readClientBuildAssets } from "../build-metadata.js";
|
|
9
|
-
import { HTTP_METHODS, type HttpMethod } from "../constants.js";
|
|
10
|
-
import { readProjectConfig, resolveProjectPath } from "../project.js";
|
|
11
|
-
|
|
12
|
-
const INSPECT_TARGETS = new Set(["routes", "api", "build", "all"]);
|
|
13
|
-
const METHOD_ORDER: HttpMethod[] = [...HTTP_METHODS];
|
|
14
|
-
|
|
15
|
-
export default defineCommand({
|
|
16
|
-
meta: {
|
|
17
|
-
name: "inspect",
|
|
18
|
-
description: "Inspect resolved app graph",
|
|
19
|
-
},
|
|
20
|
-
args: {
|
|
21
|
-
target: {
|
|
22
|
-
type: "positional",
|
|
23
|
-
description: "Inspect target: routes, api, build, or all",
|
|
24
|
-
required: false,
|
|
25
|
-
},
|
|
26
|
-
json: {
|
|
27
|
-
type: "boolean",
|
|
28
|
-
description: "Output as JSON",
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
async run({ args }) {
|
|
32
|
-
const target = args.target || "all";
|
|
33
|
-
|
|
34
|
-
if (!INSPECT_TARGETS.has(target)) {
|
|
35
|
-
handleCliError(new Error(`Unknown inspect target: ${target}`), {
|
|
36
|
-
json: Boolean(args.json),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const report = await runInspect(process.cwd(), { target });
|
|
41
|
-
|
|
42
|
-
if (args.json) {
|
|
43
|
-
console.log(JSON.stringify(report, null, 2));
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
printInspectReport(report);
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
interface InspectReport {
|
|
52
|
-
api?: { file: string; methods: string[]; path: string }[];
|
|
53
|
-
build?: {
|
|
54
|
-
adapterTarget: string;
|
|
55
|
-
clientEntryUrl: string | null;
|
|
56
|
-
cssManifest: Record<string, string[]>;
|
|
57
|
-
jsManifest: Record<string, string[]>;
|
|
58
|
-
};
|
|
59
|
-
mode: string;
|
|
60
|
-
routes?: {
|
|
61
|
-
file: string;
|
|
62
|
-
id: string;
|
|
63
|
-
loaderFile: string | null;
|
|
64
|
-
middleware: string[];
|
|
65
|
-
path: string;
|
|
66
|
-
render: string | null;
|
|
67
|
-
revalidate: unknown;
|
|
68
|
-
shell: string | null;
|
|
69
|
-
shellFile: string | null;
|
|
70
|
-
}[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function runInspect(root: string, { target = "all" } = {}): Promise<InspectReport> {
|
|
74
|
-
const project = readProjectConfig(root);
|
|
75
|
-
|
|
76
|
-
if (!project.configFile) {
|
|
77
|
-
throw new Error(
|
|
78
|
-
"Missing vite config. `pracht inspect` requires a project with pracht configured.",
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!project.hasPrachtPlugin) {
|
|
83
|
-
throw new Error("vite.config does not appear to register the pracht plugin.");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (project.mode === "manifest") {
|
|
87
|
-
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
88
|
-
if (!existsSync(manifestPath)) {
|
|
89
|
-
throw new Error(`App manifest is missing at ${project.appFile}.`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const server = await createServer({
|
|
94
|
-
root,
|
|
95
|
-
logLevel: "silent",
|
|
96
|
-
server: {
|
|
97
|
-
middlewareMode: true,
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const serverModule = await server.ssrLoadModule("virtual:pracht/server");
|
|
103
|
-
const report: InspectReport = {
|
|
104
|
-
mode: project.mode,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
if (target === "routes" || target === "all") {
|
|
108
|
-
report.routes = serializeRoutes(serverModule.resolvedApp.routes);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (target === "api" || target === "all") {
|
|
112
|
-
report.api = await Promise.all(
|
|
113
|
-
serverModule.apiRoutes.map(async (route: { file: string; path: string }) => ({
|
|
114
|
-
file: route.file,
|
|
115
|
-
methods: await detectApiMethods(server, root, route.file),
|
|
116
|
-
path: route.path,
|
|
117
|
-
})),
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (target === "build" || target === "all") {
|
|
122
|
-
const buildAssets = readClientBuildAssets(root);
|
|
123
|
-
report.build = {
|
|
124
|
-
adapterTarget: serverModule.buildTarget,
|
|
125
|
-
clientEntryUrl: buildAssets.clientEntryUrl,
|
|
126
|
-
cssManifest: buildAssets.cssManifest,
|
|
127
|
-
jsManifest: buildAssets.jsManifest,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return report;
|
|
132
|
-
} finally {
|
|
133
|
-
await server.close();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
interface RouteEntry {
|
|
138
|
-
file: string;
|
|
139
|
-
id: string;
|
|
140
|
-
loaderFile?: string;
|
|
141
|
-
middleware: string[];
|
|
142
|
-
path: string;
|
|
143
|
-
render?: string;
|
|
144
|
-
revalidate?: unknown;
|
|
145
|
-
shell?: string;
|
|
146
|
-
shellFile?: string;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function serializeRoutes(routes: RouteEntry[]) {
|
|
150
|
-
return routes.map((route) => ({
|
|
151
|
-
file: route.file,
|
|
152
|
-
id: route.id,
|
|
153
|
-
loaderFile: route.loaderFile ?? null,
|
|
154
|
-
middleware: route.middleware,
|
|
155
|
-
path: route.path,
|
|
156
|
-
render: route.render ?? null,
|
|
157
|
-
revalidate: route.revalidate ?? null,
|
|
158
|
-
shell: route.shell ?? null,
|
|
159
|
-
shellFile: route.shellFile ?? null,
|
|
160
|
-
}));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function detectApiMethods(
|
|
164
|
-
server: ViteDevServer,
|
|
165
|
-
root: string,
|
|
166
|
-
file: string,
|
|
167
|
-
): Promise<string[]> {
|
|
168
|
-
const resolvedFile = resolve(root, `.${file}`);
|
|
169
|
-
const source = readFileSync(resolvedFile, "utf-8");
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const module = await server.ssrLoadModule(file);
|
|
173
|
-
return METHOD_ORDER.filter((method) => typeof module[method] === "function");
|
|
174
|
-
} catch {
|
|
175
|
-
return METHOD_ORDER.filter((method) =>
|
|
176
|
-
new RegExp(`export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+${method}\\b`).test(source),
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function printInspectReport(report: InspectReport): void {
|
|
182
|
-
console.log(`Pracht inspect (${report.mode} mode)`);
|
|
183
|
-
|
|
184
|
-
if (report.routes) {
|
|
185
|
-
console.log("\nRoutes");
|
|
186
|
-
for (const route of report.routes) {
|
|
187
|
-
console.log(
|
|
188
|
-
` ${route.path} id=${route.id} render=${route.render ?? "n/a"} file=${route.file}`,
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (report.api) {
|
|
194
|
-
console.log("\nAPI");
|
|
195
|
-
if (report.api.length === 0) {
|
|
196
|
-
console.log(" No API routes found.");
|
|
197
|
-
} else {
|
|
198
|
-
for (const route of report.api) {
|
|
199
|
-
const methods = route.methods.length > 0 ? route.methods.join(",") : "none";
|
|
200
|
-
console.log(` ${route.path} methods=${methods} file=${route.file}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (report.build) {
|
|
206
|
-
console.log("\nBuild");
|
|
207
|
-
console.log(` adapterTarget=${report.build.adapterTarget}`);
|
|
208
|
-
console.log(` clientEntryUrl=${report.build.clientEntryUrl ?? "null"}`);
|
|
209
|
-
console.log(` cssManifestKeys=${Object.keys(report.build.cssManifest).length}`);
|
|
210
|
-
console.log(` jsManifestKeys=${Object.keys(report.build.jsManifest).length}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
package/src/commands/verify.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
import { runVerification } from "../verification.js";
|
|
4
|
-
|
|
5
|
-
export default defineCommand({
|
|
6
|
-
meta: {
|
|
7
|
-
name: "verify",
|
|
8
|
-
description: "Fast framework-aware verification",
|
|
9
|
-
},
|
|
10
|
-
args: {
|
|
11
|
-
changed: {
|
|
12
|
-
type: "boolean",
|
|
13
|
-
description: "Only check changed files",
|
|
14
|
-
},
|
|
15
|
-
json: {
|
|
16
|
-
type: "boolean",
|
|
17
|
-
description: "Output as JSON",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
async run({ args }) {
|
|
21
|
-
const report = runVerification(process.cwd(), { changed: Boolean(args.changed) });
|
|
22
|
-
|
|
23
|
-
if (args.json) {
|
|
24
|
-
console.log(JSON.stringify(report, null, 2));
|
|
25
|
-
} else {
|
|
26
|
-
console.log(`Pracht verify (${report.mode} mode, ${report.scope} scope)`);
|
|
27
|
-
for (const check of report.checks) {
|
|
28
|
-
console.log(`${check.status.toUpperCase().padEnd(7)} ${check.message}`);
|
|
29
|
-
}
|
|
30
|
-
console.log(report.ok ? "\nNo blocking issues found." : "\nBlocking issues found.");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!report.ok) {
|
|
34
|
-
process.exitCode = 1;
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
});
|
package/src/constants.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { HttpMethod } from "@pracht/core";
|
|
2
|
-
|
|
3
|
-
export type { HttpMethod };
|
|
4
|
-
|
|
5
|
-
export const VERSION = "0.0.0";
|
|
6
|
-
|
|
7
|
-
export const PROJECT_DEFAULTS = {
|
|
8
|
-
apiDir: "/src/api",
|
|
9
|
-
appFile: "/src/routes.ts",
|
|
10
|
-
middlewareDir: "/src/middleware",
|
|
11
|
-
pagesDefaultRender: "ssr",
|
|
12
|
-
pagesDir: "",
|
|
13
|
-
routesDir: "/src/routes",
|
|
14
|
-
serverDir: "/src/server",
|
|
15
|
-
shellsDir: "/src/shells",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const HTTP_METHODS = new Set<HttpMethod>([
|
|
19
|
-
"GET",
|
|
20
|
-
"POST",
|
|
21
|
-
"PUT",
|
|
22
|
-
"PATCH",
|
|
23
|
-
"DELETE",
|
|
24
|
-
"HEAD",
|
|
25
|
-
"OPTIONS",
|
|
26
|
-
]);
|
package/src/index.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { defineCommand, runMain } from "citty";
|
|
2
|
-
|
|
3
|
-
import { VERSION } from "./constants.js";
|
|
4
|
-
|
|
5
|
-
const main = defineCommand({
|
|
6
|
-
meta: {
|
|
7
|
-
name: "pracht",
|
|
8
|
-
version: VERSION,
|
|
9
|
-
description: "The pracht CLI",
|
|
10
|
-
},
|
|
11
|
-
subCommands: {
|
|
12
|
-
build: () => import("./commands/build.js").then((m) => m.default),
|
|
13
|
-
dev: () => import("./commands/dev.js").then((m) => m.default),
|
|
14
|
-
doctor: () => import("./commands/doctor.js").then((m) => m.default),
|
|
15
|
-
generate: () => import("./commands/generate.js").then((m) => m.default),
|
|
16
|
-
inspect: () => import("./commands/inspect.js").then((m) => m.default),
|
|
17
|
-
verify: () => import("./commands/verify.js").then((m) => m.default),
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
runMain(main);
|
package/src/manifest.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
export function ensureCoreNamedImport(source: string, name: string): string {
|
|
2
|
-
const match = source.match(/import\s*\{([^}]+)\}\s*from\s*["']@pracht\/core["'];?/);
|
|
3
|
-
if (!match) {
|
|
4
|
-
return `import { ${name} } from "@pracht/core";\n${source}`;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const names = match[1]
|
|
8
|
-
.split(",")
|
|
9
|
-
.map((item) => item.trim())
|
|
10
|
-
.filter(Boolean);
|
|
11
|
-
if (!names.includes(name)) {
|
|
12
|
-
names.push(name);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return source.replace(match[0], `import { ${names.join(", ")} } from "@pracht/core";`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function upsertObjectEntry(source: string, key: string, entry: string): string {
|
|
19
|
-
const property = findNamedBlock(source, key, "{", "}");
|
|
20
|
-
if (!property) {
|
|
21
|
-
const routesMatch = source.match(/^(\s*)routes\s*:/m);
|
|
22
|
-
if (!routesMatch || routesMatch.index == null) {
|
|
23
|
-
throw new Error(`Could not find a "${key}" or "routes" block in the app manifest.`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const indent = routesMatch[1];
|
|
27
|
-
const block = `${indent}${key}: {\n${indent} ${entry},\n${indent}},\n`;
|
|
28
|
-
return `${source.slice(0, routesMatch.index)}${block}${source.slice(routesMatch.index)}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return insertBlockEntry(source, property, entry);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function insertArrayItem(source: string, key: string, item: string): string {
|
|
35
|
-
const property = findNamedBlock(source, key, "[", "]");
|
|
36
|
-
if (!property) {
|
|
37
|
-
throw new Error(`Could not find "${key}" in the app manifest.`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return insertBlockEntry(source, property, item);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function toManifestModulePath(manifestPath: string, targetFilePath: string): string {
|
|
44
|
-
const relativePath = targetFilePath
|
|
45
|
-
.replaceAll("\\", "/")
|
|
46
|
-
.replace(manifestPath.replaceAll("\\", "/").replace(/\/[^/]+$/, ""), "")
|
|
47
|
-
.replace(/^\//, "");
|
|
48
|
-
|
|
49
|
-
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function extractRegistryEntries(
|
|
53
|
-
source: string,
|
|
54
|
-
key: string,
|
|
55
|
-
): { name: string; path: string }[] {
|
|
56
|
-
const block = findNamedBlock(source, key, "{", "}");
|
|
57
|
-
if (!block) return [];
|
|
58
|
-
const inner = source.slice(block.openIndex + 1, block.closeIndex);
|
|
59
|
-
const entries: { name: string; path: string }[] = [];
|
|
60
|
-
const pattern =
|
|
61
|
-
/([A-Za-z0-9_-]+)\s*:\s*(?:(["'`])([^"'`]+)\2|\(\)\s*=>\s*import\(\s*(["'`])([^"'`]+)\4\s*\))/g;
|
|
62
|
-
|
|
63
|
-
for (const match of inner.matchAll(pattern)) {
|
|
64
|
-
entries.push({ name: match[1], path: match[3] ?? match[5] });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return entries;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function extractRelativeModulePaths(source: string): Set<string> {
|
|
71
|
-
const results = new Set<string>();
|
|
72
|
-
for (const match of source.matchAll(/["'`]((?:\.\.\/|\.\/)[^"'`]+)["'`]/g)) {
|
|
73
|
-
results.add(match[1]);
|
|
74
|
-
}
|
|
75
|
-
return results;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
interface BlockLocation {
|
|
79
|
-
closeIndex: number;
|
|
80
|
-
indent: string;
|
|
81
|
-
openIndex: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function insertBlockEntry(source: string, block: BlockLocation, entry: string): string {
|
|
85
|
-
const inner = source.slice(block.openIndex + 1, block.closeIndex);
|
|
86
|
-
const closingIndent = block.indent;
|
|
87
|
-
const childIndent = `${closingIndent} `;
|
|
88
|
-
const trimmed = inner.trim();
|
|
89
|
-
|
|
90
|
-
if (!trimmed) {
|
|
91
|
-
return `${source.slice(0, block.openIndex + 1)}\n${indentMultiline(entry, childIndent)}\n${closingIndent}${source.slice(block.closeIndex)}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const needsComma = !/[,[{(]\s*$/.test(inner) && !/,\s*$/.test(trimmed);
|
|
95
|
-
const insertPrefix = needsComma ? "," : "";
|
|
96
|
-
return `${source.slice(0, block.closeIndex)}${insertPrefix}\n${indentMultiline(entry, childIndent)}\n${closingIndent}${source.slice(block.closeIndex)}`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function findNamedBlock(
|
|
100
|
-
source: string,
|
|
101
|
-
key: string,
|
|
102
|
-
openChar: string,
|
|
103
|
-
closeChar: string,
|
|
104
|
-
): BlockLocation | null {
|
|
105
|
-
const pattern = new RegExp(`^([ \\t]*)${key}\\s*:\\s*\\${openChar}`, "m");
|
|
106
|
-
const match = source.match(pattern);
|
|
107
|
-
if (!match || match.index == null) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const openIndex = source.indexOf(openChar, match.index);
|
|
112
|
-
const closeIndex = findMatchingDelimiter(source, openIndex, openChar, closeChar);
|
|
113
|
-
return {
|
|
114
|
-
closeIndex,
|
|
115
|
-
indent: match[1],
|
|
116
|
-
openIndex,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function findMatchingDelimiter(
|
|
121
|
-
source: string,
|
|
122
|
-
openIndex: number,
|
|
123
|
-
openChar: string,
|
|
124
|
-
closeChar: string,
|
|
125
|
-
): number {
|
|
126
|
-
let depth = 0;
|
|
127
|
-
let quoteChar: string | null = null;
|
|
128
|
-
let escaping = false;
|
|
129
|
-
|
|
130
|
-
for (let index = openIndex; index < source.length; index += 1) {
|
|
131
|
-
const current = source[index];
|
|
132
|
-
if (quoteChar) {
|
|
133
|
-
if (escaping) {
|
|
134
|
-
escaping = false;
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (current === "\\") {
|
|
138
|
-
escaping = true;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (current === quoteChar) {
|
|
142
|
-
quoteChar = null;
|
|
143
|
-
}
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (current === '"' || current === "'" || current === "`") {
|
|
148
|
-
quoteChar = current;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (current === openChar) depth += 1;
|
|
152
|
-
if (current === closeChar) {
|
|
153
|
-
depth -= 1;
|
|
154
|
-
if (depth === 0) return index;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
throw new Error(`Could not find matching ${closeChar} for ${openChar}.`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function indentMultiline(value: string, indent: string): string {
|
|
162
|
-
return value
|
|
163
|
-
.split("\n")
|
|
164
|
-
.map((line) => `${indent}${line}`)
|
|
165
|
-
.join("\n");
|
|
166
|
-
}
|
package/src/project.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, relative, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { ensureTrailingNewline } from "./utils.js";
|
|
5
|
-
import { PROJECT_DEFAULTS } from "./constants.js";
|
|
6
|
-
|
|
7
|
-
export interface ProjectConfig {
|
|
8
|
-
apiDir: string;
|
|
9
|
-
appFile: string;
|
|
10
|
-
configFile: string | null;
|
|
11
|
-
hasPrachtPlugin: boolean;
|
|
12
|
-
middlewareDir: string;
|
|
13
|
-
mode: "manifest" | "pages";
|
|
14
|
-
pagesDefaultRender: string;
|
|
15
|
-
pagesDir: string;
|
|
16
|
-
rawConfig: string;
|
|
17
|
-
root: string;
|
|
18
|
-
routesDir: string;
|
|
19
|
-
serverDir: string;
|
|
20
|
-
shellsDir: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function readProjectConfig(root: string): ProjectConfig {
|
|
24
|
-
const configFile = findConfigFile(root);
|
|
25
|
-
const rawConfig = configFile ? readFileSync(configFile, "utf-8") : "";
|
|
26
|
-
const config: Record<string, unknown> = {
|
|
27
|
-
...PROJECT_DEFAULTS,
|
|
28
|
-
configFile,
|
|
29
|
-
hasPrachtPlugin: /\bpracht\s*\(/.test(rawConfig),
|
|
30
|
-
mode: "manifest" as const,
|
|
31
|
-
rawConfig,
|
|
32
|
-
root,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
for (const key of Object.keys(PROJECT_DEFAULTS)) {
|
|
36
|
-
const value = readQuotedConfigValue(rawConfig, key);
|
|
37
|
-
if (typeof value === "string") {
|
|
38
|
-
config[key] = normalizeConfigPath(value);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
config.mode = config.pagesDir ? "pages" : "manifest";
|
|
43
|
-
return config as unknown as ProjectConfig;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function resolveProjectPath(root: string, configPath: string): string {
|
|
47
|
-
return resolve(root, `.${configPath}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function resolveScopedFile(root: string, configDir: string, fileName: string): string {
|
|
51
|
-
return resolve(resolveProjectPath(root, configDir), fileName);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function resolveRouteModulePath(
|
|
55
|
-
project: ProjectConfig,
|
|
56
|
-
routePath: string,
|
|
57
|
-
extension: string,
|
|
58
|
-
): { absolutePath: string; relativePath: string } {
|
|
59
|
-
const segments = segmentsFromPath(routePath);
|
|
60
|
-
const relativePath =
|
|
61
|
-
segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
|
|
62
|
-
const absolutePath = resolve(resolveProjectPath(project.root, project.routesDir), relativePath);
|
|
63
|
-
return { absolutePath, relativePath };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function resolvePagesRouteModulePath(
|
|
67
|
-
project: ProjectConfig,
|
|
68
|
-
routePath: string,
|
|
69
|
-
extension: string,
|
|
70
|
-
): { absolutePath: string; relativePath: string } {
|
|
71
|
-
const segments = segmentsFromPath(routePath);
|
|
72
|
-
const relativePath =
|
|
73
|
-
segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
|
|
74
|
-
const absolutePath = resolve(resolveProjectPath(project.root, project.pagesDir), relativePath);
|
|
75
|
-
return { absolutePath, relativePath };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function resolveApiModulePath(
|
|
79
|
-
project: ProjectConfig,
|
|
80
|
-
endpointPath: string,
|
|
81
|
-
): { absolutePath: string; relativePath: string } {
|
|
82
|
-
const segments = segmentsFromPath(endpointPath);
|
|
83
|
-
const relativePath = segments.length === 0 ? "index.ts" : `${segments.join("/")}.ts`;
|
|
84
|
-
const absolutePath = resolve(resolveProjectPath(project.root, project.apiDir), relativePath);
|
|
85
|
-
return { absolutePath, relativePath };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function displayPath(root: string, filePath: string): string {
|
|
89
|
-
return relative(root, filePath) || ".";
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function writeGeneratedFile(filePath: string, source: string): void {
|
|
93
|
-
if (existsSync(filePath)) {
|
|
94
|
-
throw new Error(`Refusing to overwrite existing file ${filePath}.`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
98
|
-
writeFileSync(filePath, ensureTrailingNewline(source), "utf-8");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function assertFileExists(filePath: string, message: string): void {
|
|
102
|
-
if (!existsSync(filePath)) {
|
|
103
|
-
throw new Error(message);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function listFilesRecursively(dir: string): string[] {
|
|
108
|
-
const files: string[] = [];
|
|
109
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
110
|
-
const fullPath = resolve(dir, entry.name);
|
|
111
|
-
if (entry.isDirectory()) {
|
|
112
|
-
files.push(...listFilesRecursively(fullPath));
|
|
113
|
-
} else {
|
|
114
|
-
files.push(fullPath);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return files;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function hasPagesAppShell(filePath: string): boolean {
|
|
121
|
-
return /^_app\.(ts|tsx|js|jsx)$/.test(basename(filePath));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function findConfigFile(root: string): string | null {
|
|
125
|
-
for (const name of [
|
|
126
|
-
"vite.config.ts",
|
|
127
|
-
"vite.config.mts",
|
|
128
|
-
"vite.config.js",
|
|
129
|
-
"vite.config.mjs",
|
|
130
|
-
"vite.config.cjs",
|
|
131
|
-
"vite.config.cts",
|
|
132
|
-
]) {
|
|
133
|
-
const file = resolve(root, name);
|
|
134
|
-
if (existsSync(file)) return file;
|
|
135
|
-
}
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function readQuotedConfigValue(source: string, key: string): string | null {
|
|
140
|
-
if (!source) return null;
|
|
141
|
-
const pattern = new RegExp(`${key}\\s*:\\s*(["'\\\`])([^"'\\\`]+)\\1`);
|
|
142
|
-
const match = source.match(pattern);
|
|
143
|
-
return match ? match[2] : null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function normalizeConfigPath(value: string): string {
|
|
147
|
-
if (!value) return value;
|
|
148
|
-
return value.startsWith("/") ? value : `/${value}`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function segmentsFromPath(path: string): string[] {
|
|
152
|
-
return path
|
|
153
|
-
.split("/")
|
|
154
|
-
.filter(Boolean)
|
|
155
|
-
.map((segment) => {
|
|
156
|
-
if (segment.startsWith(":")) return `[${segment.slice(1)}]`;
|
|
157
|
-
if (segment === "*") return "[...slug]";
|
|
158
|
-
return segment;
|
|
159
|
-
});
|
|
160
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { HTTP_METHODS, type HttpMethod } from "./constants.js";
|
|
2
|
-
|
|
3
|
-
export function quote(value: string): string {
|
|
4
|
-
return JSON.stringify(value);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function ensureTrailingNewline(value: string): string {
|
|
8
|
-
return value.endsWith("\n") ? value : `${value}\n`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function parseCommaList(value: string | string[] | boolean | undefined): string[] {
|
|
12
|
-
if (!value || typeof value === "boolean") return [];
|
|
13
|
-
const values = Array.isArray(value) ? value : [value];
|
|
14
|
-
return values
|
|
15
|
-
.flatMap((entry) => String(entry).split(","))
|
|
16
|
-
.map((entry) => entry.trim())
|
|
17
|
-
.filter(Boolean);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function parseApiMethods(value: string | string[] | boolean | undefined): HttpMethod[] {
|
|
21
|
-
const methods = parseCommaList(value);
|
|
22
|
-
const normalized =
|
|
23
|
-
methods.length === 0
|
|
24
|
-
? (["GET"] as HttpMethod[])
|
|
25
|
-
: methods.map((entry) => entry.toUpperCase() as HttpMethod);
|
|
26
|
-
|
|
27
|
-
for (const method of normalized) {
|
|
28
|
-
if (!HTTP_METHODS.has(method)) {
|
|
29
|
-
throw new Error(`Unsupported HTTP method "${method}".`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return [...new Set(normalized)];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function requireEnum(
|
|
37
|
-
value: string | undefined,
|
|
38
|
-
key: string,
|
|
39
|
-
allowed: string[],
|
|
40
|
-
fallback: string,
|
|
41
|
-
): string {
|
|
42
|
-
const val = value ?? fallback;
|
|
43
|
-
if (!allowed.includes(val)) {
|
|
44
|
-
throw new Error(`Invalid value for --${key}. Expected one of ${allowed.join(", ")}.`);
|
|
45
|
-
}
|
|
46
|
-
return val;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function requirePositiveInteger(
|
|
50
|
-
value: string | undefined,
|
|
51
|
-
key: string,
|
|
52
|
-
fallback: number,
|
|
53
|
-
): number {
|
|
54
|
-
const parsed = value == null ? fallback : Number.parseInt(value, 10);
|
|
55
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
56
|
-
throw new Error(`--${key} must be a positive integer.`);
|
|
57
|
-
}
|
|
58
|
-
return parsed;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function handleCliError(error: unknown, { json }: { json: boolean }): never {
|
|
62
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
-
if (json) {
|
|
64
|
-
console.error(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
65
|
-
} else {
|
|
66
|
-
console.error(message);
|
|
67
|
-
if (error instanceof Error && error.stack && process.env.DEBUG) {
|
|
68
|
-
console.error(error.stack);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|