@vertz/cli 0.2.15 → 0.2.17
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/dist/index.d.ts +11 -2
- package/dist/index.js +283 -63
- package/dist/vertz.js +278 -58
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -19,6 +19,8 @@ import { Command } from "commander";
|
|
|
19
19
|
import { createJiti } from "jiti";
|
|
20
20
|
|
|
21
21
|
// src/commands/build.ts
|
|
22
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
23
|
+
import { resolve as resolve3 } from "node:path";
|
|
22
24
|
import { err, ok } from "@vertz/errors";
|
|
23
25
|
|
|
24
26
|
// src/dev-server/app-detector.ts
|
|
@@ -66,10 +68,7 @@ import * as esbuild from "esbuild";
|
|
|
66
68
|
|
|
67
69
|
// src/pipeline/orchestrator.ts
|
|
68
70
|
import { createCodegenPipeline, generate } from "@vertz/codegen";
|
|
69
|
-
import {
|
|
70
|
-
createCompiler,
|
|
71
|
-
OpenAPIGenerator
|
|
72
|
-
} from "@vertz/compiler";
|
|
71
|
+
import { createCompiler, OpenAPIGenerator } from "@vertz/compiler";
|
|
73
72
|
var defaultPipelineConfig = {
|
|
74
73
|
sourceDir: "src",
|
|
75
74
|
outputDir: ".vertz/generated",
|
|
@@ -143,6 +142,13 @@ class PipelineOrchestrator {
|
|
|
143
142
|
success = false;
|
|
144
143
|
}
|
|
145
144
|
}
|
|
145
|
+
if (success) {
|
|
146
|
+
const buildUIResult = await this.runBuildUI();
|
|
147
|
+
stages.push(buildUIResult);
|
|
148
|
+
if (!buildUIResult.success) {
|
|
149
|
+
success = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
146
152
|
} catch (error) {
|
|
147
153
|
success = false;
|
|
148
154
|
stages.push({
|
|
@@ -284,12 +290,36 @@ class PipelineOrchestrator {
|
|
|
284
290
|
}
|
|
285
291
|
async runBuildUI() {
|
|
286
292
|
const startTime = performance.now();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
try {
|
|
294
|
+
if (this.config._uiCompilerValidator) {
|
|
295
|
+
const result = await this.config._uiCompilerValidator();
|
|
296
|
+
return {
|
|
297
|
+
stage: "build-ui",
|
|
298
|
+
success: true,
|
|
299
|
+
durationMs: performance.now() - startTime,
|
|
300
|
+
output: `UI compiler validated (${result.fileCount} source files)`
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const { createVertzBunPlugin } = await import("@vertz/ui-server/bun-plugin");
|
|
304
|
+
const pluginOptions = {
|
|
305
|
+
hmr: false,
|
|
306
|
+
fastRefresh: false
|
|
307
|
+
};
|
|
308
|
+
createVertzBunPlugin(pluginOptions);
|
|
309
|
+
return {
|
|
310
|
+
stage: "build-ui",
|
|
311
|
+
success: true,
|
|
312
|
+
durationMs: performance.now() - startTime,
|
|
313
|
+
output: "UI compiler validated"
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
return {
|
|
317
|
+
stage: "build-ui",
|
|
318
|
+
success: false,
|
|
319
|
+
durationMs: performance.now() - startTime,
|
|
320
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
321
|
+
};
|
|
322
|
+
}
|
|
293
323
|
}
|
|
294
324
|
async runDbSync() {
|
|
295
325
|
const startTime = performance.now();
|
|
@@ -815,11 +845,29 @@ class BuildOrchestrator {
|
|
|
815
845
|
}
|
|
816
846
|
}
|
|
817
847
|
// src/production-build/ui-build-pipeline.ts
|
|
818
|
-
import {
|
|
819
|
-
|
|
848
|
+
import {
|
|
849
|
+
cpSync,
|
|
850
|
+
existsSync as existsSync3,
|
|
851
|
+
mkdirSync as mkdirSync2,
|
|
852
|
+
readdirSync as readdirSync2,
|
|
853
|
+
readFileSync,
|
|
854
|
+
rmSync,
|
|
855
|
+
writeFileSync as writeFileSync2
|
|
856
|
+
} from "node:fs";
|
|
857
|
+
import { dirname, join as join3, resolve } from "node:path";
|
|
858
|
+
import { brotliCompressSync, constants as zlibConstants } from "node:zlib";
|
|
820
859
|
async function buildUI(config) {
|
|
821
860
|
const startTime = performance.now();
|
|
822
|
-
const {
|
|
861
|
+
const {
|
|
862
|
+
projectRoot,
|
|
863
|
+
clientEntry,
|
|
864
|
+
serverEntry,
|
|
865
|
+
outputDir,
|
|
866
|
+
minify,
|
|
867
|
+
sourcemap,
|
|
868
|
+
title = "Vertz App",
|
|
869
|
+
description
|
|
870
|
+
} = config;
|
|
823
871
|
const distDir = resolve(projectRoot, outputDir);
|
|
824
872
|
const distClient = resolve(distDir, "client");
|
|
825
873
|
const distServer = resolve(distDir, "server");
|
|
@@ -831,7 +879,8 @@ async function buildUI(config) {
|
|
|
831
879
|
console.log("\uD83D\uDCE6 Building client...");
|
|
832
880
|
const { plugin: clientPlugin, fileExtractions } = createVertzBunPlugin({
|
|
833
881
|
hmr: false,
|
|
834
|
-
fastRefresh: false
|
|
882
|
+
fastRefresh: false,
|
|
883
|
+
routeSplitting: true
|
|
835
884
|
});
|
|
836
885
|
const clientResult = await Bun.build({
|
|
837
886
|
entrypoints: [clientEntry],
|
|
@@ -855,15 +904,21 @@ ${errors}`,
|
|
|
855
904
|
}
|
|
856
905
|
let clientJsPath = "";
|
|
857
906
|
const clientCssPaths = [];
|
|
907
|
+
const chunkPaths = [];
|
|
858
908
|
for (const output of clientResult.outputs) {
|
|
859
909
|
const name = output.path.replace(distClient, "");
|
|
860
910
|
if (output.kind === "entry-point") {
|
|
861
911
|
clientJsPath = name;
|
|
862
912
|
} else if (output.path.endsWith(".css")) {
|
|
863
913
|
clientCssPaths.push(name);
|
|
914
|
+
} else if (output.kind === "chunk" && output.path.endsWith(".js")) {
|
|
915
|
+
chunkPaths.push(name);
|
|
864
916
|
}
|
|
865
917
|
}
|
|
866
918
|
console.log(` JS entry: ${clientJsPath}`);
|
|
919
|
+
for (const chunk of chunkPaths) {
|
|
920
|
+
console.log(` JS chunk: ${chunk}`);
|
|
921
|
+
}
|
|
867
922
|
for (const css of clientCssPaths) {
|
|
868
923
|
console.log(` CSS: ${css}`);
|
|
869
924
|
}
|
|
@@ -883,25 +938,44 @@ ${errors}`,
|
|
|
883
938
|
console.log("\uD83D\uDCC4 Generating HTML...");
|
|
884
939
|
const cssLinks = clientCssPaths.map((path) => ` <link rel="stylesheet" href="${path}">`).join(`
|
|
885
940
|
`);
|
|
941
|
+
const modulepreloadLinks = chunkPaths.map((path) => ` <link rel="modulepreload" href="${path}">`).join(`
|
|
942
|
+
`);
|
|
943
|
+
const descriptionTag = description ? `
|
|
944
|
+
<meta name="description" content="${description.replace(/"/g, """)}" />` : "";
|
|
945
|
+
const publicDir = resolve(projectRoot, "public");
|
|
946
|
+
const hasFavicon = existsSync3(resolve(publicDir, "favicon.svg"));
|
|
947
|
+
const hasManifest = existsSync3(resolve(publicDir, "site.webmanifest"));
|
|
948
|
+
const faviconTag = hasFavicon ? `
|
|
949
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">` : "";
|
|
950
|
+
const manifestTag = hasManifest ? `
|
|
951
|
+
<link rel="manifest" href="/site.webmanifest">` : "";
|
|
952
|
+
const themeColorTag = `
|
|
953
|
+
<meta name="theme-color" content="#0a0a0b">`;
|
|
886
954
|
const html = `<!doctype html>
|
|
887
955
|
<html lang="en">
|
|
888
956
|
<head>
|
|
889
957
|
<meta charset="UTF-8" />
|
|
890
958
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
891
|
-
<title>${title}</title
|
|
959
|
+
<title>${title}</title>${descriptionTag}${themeColorTag}${faviconTag}${manifestTag}
|
|
892
960
|
${cssLinks}
|
|
961
|
+
${modulepreloadLinks}
|
|
893
962
|
</head>
|
|
894
963
|
<body>
|
|
895
964
|
<div id="app"></div>
|
|
896
965
|
<script type="module" crossorigin src="${clientJsPath}"></script>
|
|
897
966
|
</body>
|
|
898
967
|
</html>`;
|
|
899
|
-
writeFileSync2(resolve(distClient, "
|
|
900
|
-
const publicDir = resolve(projectRoot, "public");
|
|
968
|
+
writeFileSync2(resolve(distClient, "_shell.html"), html);
|
|
901
969
|
if (existsSync3(publicDir)) {
|
|
902
970
|
cpSync(publicDir, distClient, { recursive: true });
|
|
903
971
|
console.log(" Copied public/ assets");
|
|
904
972
|
}
|
|
973
|
+
const imagesDir = resolve(projectRoot, ".vertz", "images");
|
|
974
|
+
if (existsSync3(imagesDir)) {
|
|
975
|
+
const imgDest = resolve(distClient, "__vertz_img");
|
|
976
|
+
cpSync(imagesDir, imgDest, { recursive: true });
|
|
977
|
+
console.log(" Copied optimized images");
|
|
978
|
+
}
|
|
905
979
|
console.log("\uD83D\uDCE6 Building server...");
|
|
906
980
|
const jsxSwapPlugin = {
|
|
907
981
|
name: "vertz-ssr-jsx-swap",
|
|
@@ -938,6 +1012,67 @@ ${errors}`,
|
|
|
938
1012
|
};
|
|
939
1013
|
}
|
|
940
1014
|
console.log(" Server entry: dist/server/app.js");
|
|
1015
|
+
console.log("\uD83D\uDCC4 Pre-rendering routes...");
|
|
1016
|
+
const {
|
|
1017
|
+
discoverRoutes,
|
|
1018
|
+
filterPrerenderableRoutes,
|
|
1019
|
+
prerenderRoutes,
|
|
1020
|
+
stripScriptsFromStaticHTML
|
|
1021
|
+
} = await import("@vertz/ui-server/ssr");
|
|
1022
|
+
const ssrEntryPath = resolve(distServer, "app.js");
|
|
1023
|
+
let ssrModule;
|
|
1024
|
+
try {
|
|
1025
|
+
ssrModule = await import(ssrEntryPath);
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
console.log(" ⚠ Could not import SSR module for pre-rendering, skipping.");
|
|
1028
|
+
console.log(` ${error instanceof Error ? error.message : String(error)}`);
|
|
1029
|
+
const durationMs2 = performance.now() - startTime;
|
|
1030
|
+
console.log(`
|
|
1031
|
+
✅ UI build complete (without pre-rendering)!`);
|
|
1032
|
+
console.log(` Client: ${distClient}/`);
|
|
1033
|
+
console.log(` Server: ${distServer}/`);
|
|
1034
|
+
return { success: true, durationMs: durationMs2 };
|
|
1035
|
+
}
|
|
1036
|
+
let allPatterns;
|
|
1037
|
+
try {
|
|
1038
|
+
allPatterns = await discoverRoutes(ssrModule);
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
console.log(" ⚠ Route discovery failed, skipping pre-rendering.");
|
|
1041
|
+
console.log(` ${error instanceof Error ? error.message : String(error)}`);
|
|
1042
|
+
const durationMs2 = performance.now() - startTime;
|
|
1043
|
+
console.log(`
|
|
1044
|
+
✅ UI build complete (without pre-rendering)!`);
|
|
1045
|
+
console.log(` Client: ${distClient}/`);
|
|
1046
|
+
console.log(` Server: ${distServer}/`);
|
|
1047
|
+
return { success: true, durationMs: durationMs2 };
|
|
1048
|
+
}
|
|
1049
|
+
if (allPatterns.length === 0) {
|
|
1050
|
+
console.log(" No routes discovered (app may not use createRouter).");
|
|
1051
|
+
} else {
|
|
1052
|
+
console.log(` Discovered ${allPatterns.length} route(s): ${allPatterns.join(", ")}`);
|
|
1053
|
+
const prerenderableRoutes = filterPrerenderableRoutes(allPatterns);
|
|
1054
|
+
console.log(` Pre-rendering ${prerenderableRoutes.length} static route(s)...`);
|
|
1055
|
+
if (prerenderableRoutes.length > 0) {
|
|
1056
|
+
const results = await prerenderRoutes(ssrModule, html, {
|
|
1057
|
+
routes: prerenderableRoutes
|
|
1058
|
+
});
|
|
1059
|
+
const isIslandsMode = results.some((r) => r.html.includes("data-v-island"));
|
|
1060
|
+
for (const result of results) {
|
|
1061
|
+
const outPath = result.path === "/" ? resolve(distClient, "index.html") : resolve(distClient, `${result.path.replace(/^\//, "")}/index.html`);
|
|
1062
|
+
mkdirSync2(dirname(outPath), { recursive: true });
|
|
1063
|
+
const finalHtml = isIslandsMode ? stripScriptsFromStaticHTML(result.html) : result.html;
|
|
1064
|
+
const stripped = finalHtml !== result.html;
|
|
1065
|
+
writeFileSync2(outPath, finalHtml);
|
|
1066
|
+
const suffix = stripped ? " (static — JS stripped)" : "";
|
|
1067
|
+
console.log(` ✓ ${result.path} → ${outPath.replace(distClient, "dist/client")}${suffix}`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
console.log("\uD83D\uDDDC️ Pre-compressing assets with Brotli...");
|
|
1072
|
+
const compressedCount = brotliCompressDir(distClient);
|
|
1073
|
+
if (compressedCount > 0) {
|
|
1074
|
+
console.log(` Compressed ${compressedCount} file(s)`);
|
|
1075
|
+
}
|
|
941
1076
|
const durationMs = performance.now() - startTime;
|
|
942
1077
|
console.log(`
|
|
943
1078
|
✅ UI build complete!`);
|
|
@@ -952,19 +1087,52 @@ ${errors}`,
|
|
|
952
1087
|
};
|
|
953
1088
|
}
|
|
954
1089
|
}
|
|
1090
|
+
var COMPRESSIBLE_EXTENSIONS = new Set([".html", ".js", ".css", ".svg", ".xml", ".txt", ".json"]);
|
|
1091
|
+
var MIN_COMPRESS_SIZE = 256;
|
|
1092
|
+
function brotliCompressDir(dir) {
|
|
1093
|
+
let count = 0;
|
|
1094
|
+
function walk(currentDir) {
|
|
1095
|
+
for (const entry of readdirSync2(currentDir, { withFileTypes: true })) {
|
|
1096
|
+
const fullPath = join3(currentDir, entry.name);
|
|
1097
|
+
if (entry.isDirectory()) {
|
|
1098
|
+
walk(fullPath);
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (entry.name.endsWith(".br"))
|
|
1102
|
+
continue;
|
|
1103
|
+
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
1104
|
+
if (!COMPRESSIBLE_EXTENSIONS.has(ext))
|
|
1105
|
+
continue;
|
|
1106
|
+
const content = readFileSync(fullPath);
|
|
1107
|
+
if (content.length < MIN_COMPRESS_SIZE)
|
|
1108
|
+
continue;
|
|
1109
|
+
const compressed = brotliCompressSync(content, {
|
|
1110
|
+
params: {
|
|
1111
|
+
[zlibConstants.BROTLI_PARAM_QUALITY]: zlibConstants.BROTLI_MAX_QUALITY
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
if (compressed.length < content.length) {
|
|
1115
|
+
writeFileSync2(`${fullPath}.br`, compressed);
|
|
1116
|
+
count++;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
walk(dir);
|
|
1121
|
+
return count;
|
|
1122
|
+
}
|
|
955
1123
|
// src/utils/paths.ts
|
|
956
1124
|
import { existsSync as existsSync4 } from "node:fs";
|
|
957
|
-
import { dirname, join as
|
|
1125
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
|
|
958
1126
|
var ROOT_MARKERS = ["package.json", "vertz.config.ts", "vertz.config.js"];
|
|
959
1127
|
function findProjectRoot(startDir) {
|
|
960
1128
|
let current = resolve2(startDir ?? process.cwd());
|
|
961
1129
|
while (true) {
|
|
962
1130
|
for (const marker of ROOT_MARKERS) {
|
|
963
|
-
if (existsSync4(
|
|
1131
|
+
if (existsSync4(join4(current, marker))) {
|
|
964
1132
|
return current;
|
|
965
1133
|
}
|
|
966
1134
|
}
|
|
967
|
-
const parent =
|
|
1135
|
+
const parent = dirname2(current);
|
|
968
1136
|
if (parent === current) {
|
|
969
1137
|
break;
|
|
970
1138
|
}
|
|
@@ -1100,13 +1268,23 @@ async function buildUIOnly(detected, options) {
|
|
|
1100
1268
|
console.log(` Sourcemap: ${sourcemap ? "enabled" : "disabled"}`);
|
|
1101
1269
|
console.log("");
|
|
1102
1270
|
}
|
|
1271
|
+
let title;
|
|
1272
|
+
let description;
|
|
1273
|
+
try {
|
|
1274
|
+
const pkgPath = resolve3(detected.projectRoot, "package.json");
|
|
1275
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1276
|
+
title = pkg.vertz?.title ?? pkg.title;
|
|
1277
|
+
description = pkg.vertz?.description ?? pkg.description;
|
|
1278
|
+
} catch {}
|
|
1103
1279
|
const result = await buildUI({
|
|
1104
1280
|
projectRoot: detected.projectRoot,
|
|
1105
1281
|
clientEntry: detected.clientEntry,
|
|
1106
1282
|
serverEntry,
|
|
1107
1283
|
outputDir: "dist",
|
|
1108
1284
|
minify: !noMinify,
|
|
1109
|
-
sourcemap
|
|
1285
|
+
sourcemap,
|
|
1286
|
+
title,
|
|
1287
|
+
description
|
|
1110
1288
|
});
|
|
1111
1289
|
if (!result.success) {
|
|
1112
1290
|
return err(new Error(result.error));
|
|
@@ -1302,13 +1480,13 @@ async function dbBaselineAction(options) {
|
|
|
1302
1480
|
}
|
|
1303
1481
|
|
|
1304
1482
|
// src/commands/dev.ts
|
|
1305
|
-
import { join as
|
|
1483
|
+
import { join as join6 } from "node:path";
|
|
1306
1484
|
import { err as err5, ok as ok5 } from "@vertz/errors";
|
|
1307
1485
|
|
|
1308
1486
|
// src/dev-server/fullstack-server.ts
|
|
1309
1487
|
import { spawn } from "node:child_process";
|
|
1310
1488
|
import { existsSync as existsSync5 } from "node:fs";
|
|
1311
|
-
import { join as
|
|
1489
|
+
import { join as join5, relative } from "node:path";
|
|
1312
1490
|
|
|
1313
1491
|
// src/dev-server/process-manager.ts
|
|
1314
1492
|
function createProcessManager(spawnFn) {
|
|
@@ -1345,14 +1523,14 @@ function createProcessManager(spawnFn) {
|
|
|
1345
1523
|
return;
|
|
1346
1524
|
const proc = child;
|
|
1347
1525
|
child = undefined;
|
|
1348
|
-
await new Promise((
|
|
1526
|
+
await new Promise((resolve4) => {
|
|
1349
1527
|
const timeout = setTimeout(() => {
|
|
1350
1528
|
proc.kill("SIGKILL");
|
|
1351
|
-
|
|
1529
|
+
resolve4();
|
|
1352
1530
|
}, 2000);
|
|
1353
1531
|
proc.on("exit", () => {
|
|
1354
1532
|
clearTimeout(timeout);
|
|
1355
|
-
|
|
1533
|
+
resolve4();
|
|
1356
1534
|
});
|
|
1357
1535
|
proc.kill("SIGTERM");
|
|
1358
1536
|
});
|
|
@@ -1421,7 +1599,11 @@ Expected: export default createServer({ ... })`);
|
|
|
1421
1599
|
throw new Error(`${serverEntry} default export must have a .handler function.
|
|
1422
1600
|
Expected: export default createServer({ ... })`);
|
|
1423
1601
|
}
|
|
1424
|
-
|
|
1602
|
+
let sessionResolver;
|
|
1603
|
+
if ("auth" in mod && mod.auth && typeof mod.auth.resolveSessionForSSR === "function") {
|
|
1604
|
+
sessionResolver = mod.auth.resolveSessionForSSR;
|
|
1605
|
+
}
|
|
1606
|
+
return { ...mod, sessionResolver };
|
|
1425
1607
|
}
|
|
1426
1608
|
function formatBanner(appType, port, host) {
|
|
1427
1609
|
const url = `http://${host}:${port}`;
|
|
@@ -1442,19 +1624,22 @@ async function startDevServer(options) {
|
|
|
1442
1624
|
}
|
|
1443
1625
|
const { createBunDevServer } = await import("@vertz/ui-server/bun-dev-server");
|
|
1444
1626
|
let apiHandler;
|
|
1627
|
+
let sessionResolver;
|
|
1445
1628
|
if (mode.kind === "full-stack") {
|
|
1446
1629
|
const serverMod = await importServerModule(mode.serverEntry);
|
|
1447
1630
|
apiHandler = serverMod.handler;
|
|
1631
|
+
sessionResolver = serverMod.sessionResolver;
|
|
1448
1632
|
}
|
|
1449
1633
|
const uiEntry = `./${relative(detected.projectRoot, mode.uiEntry)}`;
|
|
1450
1634
|
const clientEntry = mode.clientEntry ? `/${relative(detected.projectRoot, mode.clientEntry)}` : undefined;
|
|
1451
|
-
const openapiPath =
|
|
1635
|
+
const openapiPath = join5(detected.projectRoot, ".vertz/generated/openapi.json");
|
|
1452
1636
|
const openapi = existsSync5(openapiPath) ? { specPath: openapiPath } : undefined;
|
|
1453
1637
|
const devServer = createBunDevServer({
|
|
1454
1638
|
entry: uiEntry,
|
|
1455
1639
|
port,
|
|
1456
1640
|
host,
|
|
1457
1641
|
apiHandler,
|
|
1642
|
+
sessionResolver,
|
|
1458
1643
|
openapi,
|
|
1459
1644
|
ssrModule: mode.ssrModule,
|
|
1460
1645
|
clientEntry,
|
|
@@ -1475,10 +1660,10 @@ function startApiOnlyServer(serverEntry, port) {
|
|
|
1475
1660
|
stdio: "inherit"
|
|
1476
1661
|
}));
|
|
1477
1662
|
pm.start(serverEntry, { PORT: String(port) });
|
|
1478
|
-
return new Promise((
|
|
1663
|
+
return new Promise((resolve4) => {
|
|
1479
1664
|
const shutdown = async () => {
|
|
1480
1665
|
await pm.stop();
|
|
1481
|
-
|
|
1666
|
+
resolve4();
|
|
1482
1667
|
};
|
|
1483
1668
|
process.on("SIGINT", shutdown);
|
|
1484
1669
|
process.on("SIGTERM", shutdown);
|
|
@@ -1552,7 +1737,7 @@ async function devAction(options = {}) {
|
|
|
1552
1737
|
}
|
|
1553
1738
|
}
|
|
1554
1739
|
const _watcher = createPipelineWatcher({
|
|
1555
|
-
dir:
|
|
1740
|
+
dir: join6(projectRoot, "src"),
|
|
1556
1741
|
debounceMs: 100,
|
|
1557
1742
|
onChange: async (changes) => {
|
|
1558
1743
|
if (!isRunning)
|
|
@@ -1689,34 +1874,35 @@ function generateAction(options) {
|
|
|
1689
1874
|
}
|
|
1690
1875
|
|
|
1691
1876
|
// src/commands/start.ts
|
|
1692
|
-
import { existsSync as existsSync6, readdirSync as
|
|
1693
|
-
import { join as
|
|
1877
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
1878
|
+
import { join as join7, resolve as resolve4 } from "node:path";
|
|
1694
1879
|
import { err as err7, ok as ok7 } from "@vertz/errors";
|
|
1695
1880
|
function discoverSSRModule(projectRoot) {
|
|
1696
|
-
const serverDir =
|
|
1881
|
+
const serverDir = join7(projectRoot, "dist", "server");
|
|
1697
1882
|
if (!existsSync6(serverDir))
|
|
1698
1883
|
return;
|
|
1699
|
-
const files =
|
|
1884
|
+
const files = readdirSync3(serverDir).filter((f) => f.endsWith(".js"));
|
|
1700
1885
|
if (files.length === 0)
|
|
1701
1886
|
return;
|
|
1702
1887
|
if (files.includes("app.js")) {
|
|
1703
|
-
return
|
|
1888
|
+
return join7(serverDir, "app.js");
|
|
1704
1889
|
}
|
|
1705
1890
|
const first = files[0];
|
|
1706
|
-
return first ?
|
|
1891
|
+
return first ? join7(serverDir, first) : undefined;
|
|
1707
1892
|
}
|
|
1708
1893
|
function validateBuildOutputs(projectRoot, appType) {
|
|
1709
1894
|
const missing = [];
|
|
1710
1895
|
if (appType === "api-only" || appType === "full-stack") {
|
|
1711
|
-
const apiBuild =
|
|
1896
|
+
const apiBuild = join7(projectRoot, ".vertz", "build", "index.js");
|
|
1712
1897
|
if (!existsSync6(apiBuild)) {
|
|
1713
1898
|
missing.push(".vertz/build/index.js");
|
|
1714
1899
|
}
|
|
1715
1900
|
}
|
|
1716
1901
|
if (appType === "ui-only" || appType === "full-stack") {
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1902
|
+
const shellHtml = join7(projectRoot, "dist", "client", "_shell.html");
|
|
1903
|
+
const clientHtml = join7(projectRoot, "dist", "client", "index.html");
|
|
1904
|
+
if (!existsSync6(shellHtml) && !existsSync6(clientHtml)) {
|
|
1905
|
+
missing.push("dist/client/_shell.html");
|
|
1720
1906
|
}
|
|
1721
1907
|
const ssrModule = discoverSSRModule(projectRoot);
|
|
1722
1908
|
if (!ssrModule) {
|
|
@@ -1761,7 +1947,7 @@ async function startAction(options = {}) {
|
|
|
1761
1947
|
}
|
|
1762
1948
|
}
|
|
1763
1949
|
async function startApiOnly(projectRoot, port, host, _verbose) {
|
|
1764
|
-
const entryPath =
|
|
1950
|
+
const entryPath = resolve4(projectRoot, ".vertz", "build", "index.js");
|
|
1765
1951
|
let mod;
|
|
1766
1952
|
try {
|
|
1767
1953
|
mod = await import(entryPath);
|
|
@@ -1786,8 +1972,10 @@ async function startUIOnly(projectRoot, port, host, _verbose) {
|
|
|
1786
1972
|
if (!ssrModulePath) {
|
|
1787
1973
|
return err7(new Error('No SSR module found in dist/server/. Run "vertz build" first.'));
|
|
1788
1974
|
}
|
|
1789
|
-
const
|
|
1790
|
-
const
|
|
1975
|
+
const shellPath = resolve4(projectRoot, "dist", "client", "_shell.html");
|
|
1976
|
+
const legacyPath = resolve4(projectRoot, "dist", "client", "index.html");
|
|
1977
|
+
const templatePath = existsSync6(shellPath) ? shellPath : legacyPath;
|
|
1978
|
+
const template = readFileSync3(templatePath, "utf-8");
|
|
1791
1979
|
let ssrModule;
|
|
1792
1980
|
try {
|
|
1793
1981
|
ssrModule = await import(ssrModulePath);
|
|
@@ -1801,16 +1989,22 @@ async function startUIOnly(projectRoot, port, host, _verbose) {
|
|
|
1801
1989
|
template,
|
|
1802
1990
|
inlineCSS
|
|
1803
1991
|
});
|
|
1804
|
-
const clientDir =
|
|
1992
|
+
const clientDir = resolve4(projectRoot, "dist", "client");
|
|
1805
1993
|
const server = Bun.serve({
|
|
1806
1994
|
port,
|
|
1807
1995
|
hostname: host,
|
|
1808
1996
|
async fetch(req) {
|
|
1809
1997
|
const url = new URL(req.url);
|
|
1810
1998
|
const pathname = url.pathname;
|
|
1999
|
+
if (req.headers.get("x-vertz-nav") === "1") {
|
|
2000
|
+
return ssrHandler(req);
|
|
2001
|
+
}
|
|
1811
2002
|
const staticResponse = serveStaticFile(clientDir, pathname);
|
|
1812
2003
|
if (staticResponse)
|
|
1813
2004
|
return staticResponse;
|
|
2005
|
+
const prerenderResponse = servePrerenderHTML(clientDir, pathname);
|
|
2006
|
+
if (prerenderResponse)
|
|
2007
|
+
return prerenderResponse;
|
|
1814
2008
|
return ssrHandler(req);
|
|
1815
2009
|
}
|
|
1816
2010
|
});
|
|
@@ -1819,7 +2013,7 @@ async function startUIOnly(projectRoot, port, host, _verbose) {
|
|
|
1819
2013
|
return ok7(undefined);
|
|
1820
2014
|
}
|
|
1821
2015
|
async function startFullStack(projectRoot, port, host, _verbose) {
|
|
1822
|
-
const apiEntryPath =
|
|
2016
|
+
const apiEntryPath = resolve4(projectRoot, ".vertz", "build", "index.js");
|
|
1823
2017
|
let apiMod;
|
|
1824
2018
|
try {
|
|
1825
2019
|
apiMod = await import(apiEntryPath);
|
|
@@ -1834,8 +2028,10 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1834
2028
|
if (!ssrModulePath) {
|
|
1835
2029
|
return err7(new Error('No SSR module found in dist/server/. Run "vertz build" first.'));
|
|
1836
2030
|
}
|
|
1837
|
-
const
|
|
1838
|
-
const
|
|
2031
|
+
const shellPath = resolve4(projectRoot, "dist", "client", "_shell.html");
|
|
2032
|
+
const legacyPath = resolve4(projectRoot, "dist", "client", "index.html");
|
|
2033
|
+
const templatePath = existsSync6(shellPath) ? shellPath : legacyPath;
|
|
2034
|
+
const template = readFileSync3(templatePath, "utf-8");
|
|
1839
2035
|
let ssrModule;
|
|
1840
2036
|
try {
|
|
1841
2037
|
ssrModule = await import(ssrModulePath);
|
|
@@ -1849,7 +2045,7 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1849
2045
|
template,
|
|
1850
2046
|
inlineCSS
|
|
1851
2047
|
});
|
|
1852
|
-
const clientDir =
|
|
2048
|
+
const clientDir = resolve4(projectRoot, "dist", "client");
|
|
1853
2049
|
const server = Bun.serve({
|
|
1854
2050
|
port,
|
|
1855
2051
|
hostname: host,
|
|
@@ -1859,9 +2055,15 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1859
2055
|
if (pathname.startsWith("/api")) {
|
|
1860
2056
|
return apiHandler(req);
|
|
1861
2057
|
}
|
|
2058
|
+
if (req.headers.get("x-vertz-nav") === "1") {
|
|
2059
|
+
return ssrHandler(req);
|
|
2060
|
+
}
|
|
1862
2061
|
const staticResponse = serveStaticFile(clientDir, pathname);
|
|
1863
2062
|
if (staticResponse)
|
|
1864
2063
|
return staticResponse;
|
|
2064
|
+
const prerenderResponse = servePrerenderHTML(clientDir, pathname);
|
|
2065
|
+
if (prerenderResponse)
|
|
2066
|
+
return prerenderResponse;
|
|
1865
2067
|
return ssrHandler(req);
|
|
1866
2068
|
}
|
|
1867
2069
|
});
|
|
@@ -1870,25 +2072,43 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1870
2072
|
return ok7(undefined);
|
|
1871
2073
|
}
|
|
1872
2074
|
function discoverInlineCSS(projectRoot) {
|
|
1873
|
-
const cssDir =
|
|
2075
|
+
const cssDir = resolve4(projectRoot, "dist", "client", "assets");
|
|
1874
2076
|
if (!existsSync6(cssDir))
|
|
1875
2077
|
return;
|
|
1876
|
-
const cssFiles =
|
|
2078
|
+
const cssFiles = readdirSync3(cssDir).filter((f) => f.endsWith(".css"));
|
|
1877
2079
|
if (cssFiles.length === 0)
|
|
1878
2080
|
return;
|
|
1879
2081
|
const result = {};
|
|
1880
2082
|
for (const file of cssFiles) {
|
|
1881
|
-
const content =
|
|
2083
|
+
const content = readFileSync3(join7(cssDir, file), "utf-8");
|
|
1882
2084
|
result[`/assets/${file}`] = content;
|
|
1883
2085
|
}
|
|
1884
2086
|
return result;
|
|
1885
2087
|
}
|
|
2088
|
+
function servePrerenderHTML(clientDir, pathname) {
|
|
2089
|
+
const htmlPath = pathname === "/" ? resolve4(clientDir, "index.html") : resolve4(clientDir, `${pathname.replace(/^\//, "")}/index.html`);
|
|
2090
|
+
if (!htmlPath.startsWith(clientDir))
|
|
2091
|
+
return null;
|
|
2092
|
+
if (htmlPath.endsWith("/_shell.html"))
|
|
2093
|
+
return null;
|
|
2094
|
+
const file = Bun.file(htmlPath);
|
|
2095
|
+
if (!file.size)
|
|
2096
|
+
return null;
|
|
2097
|
+
return new Response(file, {
|
|
2098
|
+
headers: {
|
|
2099
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
2100
|
+
"Cache-Control": "public, max-age=0, must-revalidate"
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
1886
2104
|
function serveStaticFile(clientDir, pathname) {
|
|
1887
2105
|
if (pathname === "/" || pathname === "/index.html")
|
|
1888
2106
|
return null;
|
|
1889
|
-
const filePath =
|
|
2107
|
+
const filePath = resolve4(clientDir, `.${pathname}`);
|
|
1890
2108
|
if (!filePath.startsWith(clientDir))
|
|
1891
2109
|
return null;
|
|
2110
|
+
if (!existsSync6(filePath) || !statSync2(filePath).isFile())
|
|
2111
|
+
return null;
|
|
1892
2112
|
const file = Bun.file(filePath);
|
|
1893
2113
|
if (!file.size)
|
|
1894
2114
|
return null;
|
|
@@ -1983,12 +2203,12 @@ function createCLI() {
|
|
|
1983
2203
|
}
|
|
1984
2204
|
});
|
|
1985
2205
|
program.command("codegen").description("Generate SDK and CLI clients from the compiled API").option("--dry-run", "Preview generated files without writing").option("--output <dir>", "Output directory").action(async (opts) => {
|
|
1986
|
-
const { resolve:
|
|
2206
|
+
const { resolve: resolve5, dirname: dirname3 } = await import("node:path");
|
|
1987
2207
|
const { mkdir, writeFile: fsWriteFile } = await import("node:fs/promises");
|
|
1988
2208
|
let config;
|
|
1989
2209
|
let compilerConfig;
|
|
1990
2210
|
try {
|
|
1991
|
-
const configPath =
|
|
2211
|
+
const configPath = resolve5(process.cwd(), "vertz.config.ts");
|
|
1992
2212
|
const jiti = createJiti(import.meta.url);
|
|
1993
2213
|
const configModule = await jiti.import(configPath);
|
|
1994
2214
|
config = configModule.codegen ?? configModule.default?.codegen;
|
|
@@ -2013,7 +2233,7 @@ function createCLI() {
|
|
|
2013
2233
|
const { createCodegenPipeline: createCodegenPipeline2 } = await import("@vertz/codegen");
|
|
2014
2234
|
const pipeline = createCodegenPipeline2();
|
|
2015
2235
|
const writeFile = async (path, content) => {
|
|
2016
|
-
await mkdir(
|
|
2236
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
2017
2237
|
await fsWriteFile(path, content, "utf-8");
|
|
2018
2238
|
};
|
|
2019
2239
|
const result = await codegenAction({
|
|
@@ -2114,8 +2334,8 @@ Schema is in sync.`);
|
|
|
2114
2334
|
if (!opts.force) {
|
|
2115
2335
|
const readline = await import("node:readline");
|
|
2116
2336
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2117
|
-
const answer = await new Promise((
|
|
2118
|
-
rl.question("This will drop all tables and re-apply migrations. Continue? (y/N) ",
|
|
2337
|
+
const answer = await new Promise((resolve5) => {
|
|
2338
|
+
rl.question("This will drop all tables and re-apply migrations. Continue? (y/N) ", resolve5);
|
|
2119
2339
|
});
|
|
2120
2340
|
rl.close();
|
|
2121
2341
|
if (answer.toLowerCase() !== "y") {
|
|
@@ -2429,12 +2649,12 @@ var defaultCLIConfig = {
|
|
|
2429
2649
|
};
|
|
2430
2650
|
// src/config/loader.ts
|
|
2431
2651
|
import { existsSync as existsSync7 } from "node:fs";
|
|
2432
|
-
import { join as
|
|
2652
|
+
import { join as join8, resolve as resolve5 } from "node:path";
|
|
2433
2653
|
var CONFIG_FILES = ["vertz.config.ts", "vertz.config.js", "vertz.config.mjs"];
|
|
2434
2654
|
function findConfigFile(startDir) {
|
|
2435
|
-
const dir =
|
|
2655
|
+
const dir = resolve5(startDir ?? process.cwd());
|
|
2436
2656
|
for (const filename of CONFIG_FILES) {
|
|
2437
|
-
const filepath =
|
|
2657
|
+
const filepath = join8(dir, filename);
|
|
2438
2658
|
if (existsSync7(filepath)) {
|
|
2439
2659
|
return filepath;
|
|
2440
2660
|
}
|
|
@@ -2476,7 +2696,7 @@ async function loadConfig(configPath) {
|
|
|
2476
2696
|
return deepMerge(defaultConfig, userConfig);
|
|
2477
2697
|
}
|
|
2478
2698
|
// src/deploy/detector.ts
|
|
2479
|
-
import { join as
|
|
2699
|
+
import { join as join9 } from "node:path";
|
|
2480
2700
|
var DETECTION_ORDER = [
|
|
2481
2701
|
{ file: "fly.toml", target: "fly" },
|
|
2482
2702
|
{ file: "railway.toml", target: "railway" },
|
|
@@ -2484,7 +2704,7 @@ var DETECTION_ORDER = [
|
|
|
2484
2704
|
];
|
|
2485
2705
|
function detectTarget(projectRoot, existsFn) {
|
|
2486
2706
|
for (const { file, target } of DETECTION_ORDER) {
|
|
2487
|
-
if (existsFn(
|
|
2707
|
+
if (existsFn(join9(projectRoot, file))) {
|
|
2488
2708
|
return target;
|
|
2489
2709
|
}
|
|
2490
2710
|
}
|