@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/vertz.js
CHANGED
|
@@ -9,6 +9,8 @@ import { Command } from "commander";
|
|
|
9
9
|
import { createJiti } from "jiti";
|
|
10
10
|
|
|
11
11
|
// src/commands/build.ts
|
|
12
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
13
|
+
import { resolve as resolve3 } from "node:path";
|
|
12
14
|
import { err, ok } from "@vertz/errors";
|
|
13
15
|
|
|
14
16
|
// src/dev-server/app-detector.ts
|
|
@@ -56,10 +58,7 @@ import * as esbuild from "esbuild";
|
|
|
56
58
|
|
|
57
59
|
// src/pipeline/orchestrator.ts
|
|
58
60
|
import { createCodegenPipeline, generate } from "@vertz/codegen";
|
|
59
|
-
import {
|
|
60
|
-
createCompiler,
|
|
61
|
-
OpenAPIGenerator
|
|
62
|
-
} from "@vertz/compiler";
|
|
61
|
+
import { createCompiler, OpenAPIGenerator } from "@vertz/compiler";
|
|
63
62
|
var defaultPipelineConfig = {
|
|
64
63
|
sourceDir: "src",
|
|
65
64
|
outputDir: ".vertz/generated",
|
|
@@ -133,6 +132,13 @@ class PipelineOrchestrator {
|
|
|
133
132
|
success = false;
|
|
134
133
|
}
|
|
135
134
|
}
|
|
135
|
+
if (success) {
|
|
136
|
+
const buildUIResult = await this.runBuildUI();
|
|
137
|
+
stages.push(buildUIResult);
|
|
138
|
+
if (!buildUIResult.success) {
|
|
139
|
+
success = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
136
142
|
} catch (error) {
|
|
137
143
|
success = false;
|
|
138
144
|
stages.push({
|
|
@@ -274,12 +280,36 @@ class PipelineOrchestrator {
|
|
|
274
280
|
}
|
|
275
281
|
async runBuildUI() {
|
|
276
282
|
const startTime = performance.now();
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
+
try {
|
|
284
|
+
if (this.config._uiCompilerValidator) {
|
|
285
|
+
const result = await this.config._uiCompilerValidator();
|
|
286
|
+
return {
|
|
287
|
+
stage: "build-ui",
|
|
288
|
+
success: true,
|
|
289
|
+
durationMs: performance.now() - startTime,
|
|
290
|
+
output: `UI compiler validated (${result.fileCount} source files)`
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const { createVertzBunPlugin } = await import("@vertz/ui-server/bun-plugin");
|
|
294
|
+
const pluginOptions = {
|
|
295
|
+
hmr: false,
|
|
296
|
+
fastRefresh: false
|
|
297
|
+
};
|
|
298
|
+
createVertzBunPlugin(pluginOptions);
|
|
299
|
+
return {
|
|
300
|
+
stage: "build-ui",
|
|
301
|
+
success: true,
|
|
302
|
+
durationMs: performance.now() - startTime,
|
|
303
|
+
output: "UI compiler validated"
|
|
304
|
+
};
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return {
|
|
307
|
+
stage: "build-ui",
|
|
308
|
+
success: false,
|
|
309
|
+
durationMs: performance.now() - startTime,
|
|
310
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
311
|
+
};
|
|
312
|
+
}
|
|
283
313
|
}
|
|
284
314
|
async runDbSync() {
|
|
285
315
|
const startTime = performance.now();
|
|
@@ -791,11 +821,29 @@ class BuildOrchestrator {
|
|
|
791
821
|
}
|
|
792
822
|
}
|
|
793
823
|
// src/production-build/ui-build-pipeline.ts
|
|
794
|
-
import {
|
|
795
|
-
|
|
824
|
+
import {
|
|
825
|
+
cpSync,
|
|
826
|
+
existsSync as existsSync3,
|
|
827
|
+
mkdirSync as mkdirSync2,
|
|
828
|
+
readdirSync as readdirSync2,
|
|
829
|
+
readFileSync,
|
|
830
|
+
rmSync,
|
|
831
|
+
writeFileSync as writeFileSync2
|
|
832
|
+
} from "node:fs";
|
|
833
|
+
import { dirname, join as join3, resolve } from "node:path";
|
|
834
|
+
import { brotliCompressSync, constants as zlibConstants } from "node:zlib";
|
|
796
835
|
async function buildUI(config) {
|
|
797
836
|
const startTime = performance.now();
|
|
798
|
-
const {
|
|
837
|
+
const {
|
|
838
|
+
projectRoot,
|
|
839
|
+
clientEntry,
|
|
840
|
+
serverEntry,
|
|
841
|
+
outputDir,
|
|
842
|
+
minify,
|
|
843
|
+
sourcemap,
|
|
844
|
+
title = "Vertz App",
|
|
845
|
+
description
|
|
846
|
+
} = config;
|
|
799
847
|
const distDir = resolve(projectRoot, outputDir);
|
|
800
848
|
const distClient = resolve(distDir, "client");
|
|
801
849
|
const distServer = resolve(distDir, "server");
|
|
@@ -807,7 +855,8 @@ async function buildUI(config) {
|
|
|
807
855
|
console.log("\uD83D\uDCE6 Building client...");
|
|
808
856
|
const { plugin: clientPlugin, fileExtractions } = createVertzBunPlugin({
|
|
809
857
|
hmr: false,
|
|
810
|
-
fastRefresh: false
|
|
858
|
+
fastRefresh: false,
|
|
859
|
+
routeSplitting: true
|
|
811
860
|
});
|
|
812
861
|
const clientResult = await Bun.build({
|
|
813
862
|
entrypoints: [clientEntry],
|
|
@@ -831,15 +880,21 @@ ${errors}`,
|
|
|
831
880
|
}
|
|
832
881
|
let clientJsPath = "";
|
|
833
882
|
const clientCssPaths = [];
|
|
883
|
+
const chunkPaths = [];
|
|
834
884
|
for (const output of clientResult.outputs) {
|
|
835
885
|
const name = output.path.replace(distClient, "");
|
|
836
886
|
if (output.kind === "entry-point") {
|
|
837
887
|
clientJsPath = name;
|
|
838
888
|
} else if (output.path.endsWith(".css")) {
|
|
839
889
|
clientCssPaths.push(name);
|
|
890
|
+
} else if (output.kind === "chunk" && output.path.endsWith(".js")) {
|
|
891
|
+
chunkPaths.push(name);
|
|
840
892
|
}
|
|
841
893
|
}
|
|
842
894
|
console.log(` JS entry: ${clientJsPath}`);
|
|
895
|
+
for (const chunk of chunkPaths) {
|
|
896
|
+
console.log(` JS chunk: ${chunk}`);
|
|
897
|
+
}
|
|
843
898
|
for (const css of clientCssPaths) {
|
|
844
899
|
console.log(` CSS: ${css}`);
|
|
845
900
|
}
|
|
@@ -859,25 +914,44 @@ ${errors}`,
|
|
|
859
914
|
console.log("\uD83D\uDCC4 Generating HTML...");
|
|
860
915
|
const cssLinks = clientCssPaths.map((path) => ` <link rel="stylesheet" href="${path}">`).join(`
|
|
861
916
|
`);
|
|
917
|
+
const modulepreloadLinks = chunkPaths.map((path) => ` <link rel="modulepreload" href="${path}">`).join(`
|
|
918
|
+
`);
|
|
919
|
+
const descriptionTag = description ? `
|
|
920
|
+
<meta name="description" content="${description.replace(/"/g, """)}" />` : "";
|
|
921
|
+
const publicDir = resolve(projectRoot, "public");
|
|
922
|
+
const hasFavicon = existsSync3(resolve(publicDir, "favicon.svg"));
|
|
923
|
+
const hasManifest = existsSync3(resolve(publicDir, "site.webmanifest"));
|
|
924
|
+
const faviconTag = hasFavicon ? `
|
|
925
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">` : "";
|
|
926
|
+
const manifestTag = hasManifest ? `
|
|
927
|
+
<link rel="manifest" href="/site.webmanifest">` : "";
|
|
928
|
+
const themeColorTag = `
|
|
929
|
+
<meta name="theme-color" content="#0a0a0b">`;
|
|
862
930
|
const html = `<!doctype html>
|
|
863
931
|
<html lang="en">
|
|
864
932
|
<head>
|
|
865
933
|
<meta charset="UTF-8" />
|
|
866
934
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
867
|
-
<title>${title}</title
|
|
935
|
+
<title>${title}</title>${descriptionTag}${themeColorTag}${faviconTag}${manifestTag}
|
|
868
936
|
${cssLinks}
|
|
937
|
+
${modulepreloadLinks}
|
|
869
938
|
</head>
|
|
870
939
|
<body>
|
|
871
940
|
<div id="app"></div>
|
|
872
941
|
<script type="module" crossorigin src="${clientJsPath}"></script>
|
|
873
942
|
</body>
|
|
874
943
|
</html>`;
|
|
875
|
-
writeFileSync2(resolve(distClient, "
|
|
876
|
-
const publicDir = resolve(projectRoot, "public");
|
|
944
|
+
writeFileSync2(resolve(distClient, "_shell.html"), html);
|
|
877
945
|
if (existsSync3(publicDir)) {
|
|
878
946
|
cpSync(publicDir, distClient, { recursive: true });
|
|
879
947
|
console.log(" Copied public/ assets");
|
|
880
948
|
}
|
|
949
|
+
const imagesDir = resolve(projectRoot, ".vertz", "images");
|
|
950
|
+
if (existsSync3(imagesDir)) {
|
|
951
|
+
const imgDest = resolve(distClient, "__vertz_img");
|
|
952
|
+
cpSync(imagesDir, imgDest, { recursive: true });
|
|
953
|
+
console.log(" Copied optimized images");
|
|
954
|
+
}
|
|
881
955
|
console.log("\uD83D\uDCE6 Building server...");
|
|
882
956
|
const jsxSwapPlugin = {
|
|
883
957
|
name: "vertz-ssr-jsx-swap",
|
|
@@ -914,6 +988,67 @@ ${errors}`,
|
|
|
914
988
|
};
|
|
915
989
|
}
|
|
916
990
|
console.log(" Server entry: dist/server/app.js");
|
|
991
|
+
console.log("\uD83D\uDCC4 Pre-rendering routes...");
|
|
992
|
+
const {
|
|
993
|
+
discoverRoutes,
|
|
994
|
+
filterPrerenderableRoutes,
|
|
995
|
+
prerenderRoutes,
|
|
996
|
+
stripScriptsFromStaticHTML
|
|
997
|
+
} = await import("@vertz/ui-server/ssr");
|
|
998
|
+
const ssrEntryPath = resolve(distServer, "app.js");
|
|
999
|
+
let ssrModule;
|
|
1000
|
+
try {
|
|
1001
|
+
ssrModule = await import(ssrEntryPath);
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
console.log(" ⚠ Could not import SSR module for pre-rendering, skipping.");
|
|
1004
|
+
console.log(` ${error instanceof Error ? error.message : String(error)}`);
|
|
1005
|
+
const durationMs2 = performance.now() - startTime;
|
|
1006
|
+
console.log(`
|
|
1007
|
+
✅ UI build complete (without pre-rendering)!`);
|
|
1008
|
+
console.log(` Client: ${distClient}/`);
|
|
1009
|
+
console.log(` Server: ${distServer}/`);
|
|
1010
|
+
return { success: true, durationMs: durationMs2 };
|
|
1011
|
+
}
|
|
1012
|
+
let allPatterns;
|
|
1013
|
+
try {
|
|
1014
|
+
allPatterns = await discoverRoutes(ssrModule);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
console.log(" ⚠ Route discovery failed, skipping pre-rendering.");
|
|
1017
|
+
console.log(` ${error instanceof Error ? error.message : String(error)}`);
|
|
1018
|
+
const durationMs2 = performance.now() - startTime;
|
|
1019
|
+
console.log(`
|
|
1020
|
+
✅ UI build complete (without pre-rendering)!`);
|
|
1021
|
+
console.log(` Client: ${distClient}/`);
|
|
1022
|
+
console.log(` Server: ${distServer}/`);
|
|
1023
|
+
return { success: true, durationMs: durationMs2 };
|
|
1024
|
+
}
|
|
1025
|
+
if (allPatterns.length === 0) {
|
|
1026
|
+
console.log(" No routes discovered (app may not use createRouter).");
|
|
1027
|
+
} else {
|
|
1028
|
+
console.log(` Discovered ${allPatterns.length} route(s): ${allPatterns.join(", ")}`);
|
|
1029
|
+
const prerenderableRoutes = filterPrerenderableRoutes(allPatterns);
|
|
1030
|
+
console.log(` Pre-rendering ${prerenderableRoutes.length} static route(s)...`);
|
|
1031
|
+
if (prerenderableRoutes.length > 0) {
|
|
1032
|
+
const results = await prerenderRoutes(ssrModule, html, {
|
|
1033
|
+
routes: prerenderableRoutes
|
|
1034
|
+
});
|
|
1035
|
+
const isIslandsMode = results.some((r) => r.html.includes("data-v-island"));
|
|
1036
|
+
for (const result of results) {
|
|
1037
|
+
const outPath = result.path === "/" ? resolve(distClient, "index.html") : resolve(distClient, `${result.path.replace(/^\//, "")}/index.html`);
|
|
1038
|
+
mkdirSync2(dirname(outPath), { recursive: true });
|
|
1039
|
+
const finalHtml = isIslandsMode ? stripScriptsFromStaticHTML(result.html) : result.html;
|
|
1040
|
+
const stripped = finalHtml !== result.html;
|
|
1041
|
+
writeFileSync2(outPath, finalHtml);
|
|
1042
|
+
const suffix = stripped ? " (static — JS stripped)" : "";
|
|
1043
|
+
console.log(` ✓ ${result.path} → ${outPath.replace(distClient, "dist/client")}${suffix}`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
console.log("\uD83D\uDDDC️ Pre-compressing assets with Brotli...");
|
|
1048
|
+
const compressedCount = brotliCompressDir(distClient);
|
|
1049
|
+
if (compressedCount > 0) {
|
|
1050
|
+
console.log(` Compressed ${compressedCount} file(s)`);
|
|
1051
|
+
}
|
|
917
1052
|
const durationMs = performance.now() - startTime;
|
|
918
1053
|
console.log(`
|
|
919
1054
|
✅ UI build complete!`);
|
|
@@ -928,19 +1063,52 @@ ${errors}`,
|
|
|
928
1063
|
};
|
|
929
1064
|
}
|
|
930
1065
|
}
|
|
1066
|
+
var COMPRESSIBLE_EXTENSIONS = new Set([".html", ".js", ".css", ".svg", ".xml", ".txt", ".json"]);
|
|
1067
|
+
var MIN_COMPRESS_SIZE = 256;
|
|
1068
|
+
function brotliCompressDir(dir) {
|
|
1069
|
+
let count = 0;
|
|
1070
|
+
function walk(currentDir) {
|
|
1071
|
+
for (const entry of readdirSync2(currentDir, { withFileTypes: true })) {
|
|
1072
|
+
const fullPath = join3(currentDir, entry.name);
|
|
1073
|
+
if (entry.isDirectory()) {
|
|
1074
|
+
walk(fullPath);
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (entry.name.endsWith(".br"))
|
|
1078
|
+
continue;
|
|
1079
|
+
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
1080
|
+
if (!COMPRESSIBLE_EXTENSIONS.has(ext))
|
|
1081
|
+
continue;
|
|
1082
|
+
const content = readFileSync(fullPath);
|
|
1083
|
+
if (content.length < MIN_COMPRESS_SIZE)
|
|
1084
|
+
continue;
|
|
1085
|
+
const compressed = brotliCompressSync(content, {
|
|
1086
|
+
params: {
|
|
1087
|
+
[zlibConstants.BROTLI_PARAM_QUALITY]: zlibConstants.BROTLI_MAX_QUALITY
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
if (compressed.length < content.length) {
|
|
1091
|
+
writeFileSync2(`${fullPath}.br`, compressed);
|
|
1092
|
+
count++;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
walk(dir);
|
|
1097
|
+
return count;
|
|
1098
|
+
}
|
|
931
1099
|
// src/utils/paths.ts
|
|
932
1100
|
import { existsSync as existsSync4 } from "node:fs";
|
|
933
|
-
import { dirname, join as
|
|
1101
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
|
|
934
1102
|
var ROOT_MARKERS = ["package.json", "vertz.config.ts", "vertz.config.js"];
|
|
935
1103
|
function findProjectRoot(startDir) {
|
|
936
1104
|
let current = resolve2(startDir ?? process.cwd());
|
|
937
1105
|
while (true) {
|
|
938
1106
|
for (const marker of ROOT_MARKERS) {
|
|
939
|
-
if (existsSync4(
|
|
1107
|
+
if (existsSync4(join4(current, marker))) {
|
|
940
1108
|
return current;
|
|
941
1109
|
}
|
|
942
1110
|
}
|
|
943
|
-
const parent =
|
|
1111
|
+
const parent = dirname2(current);
|
|
944
1112
|
if (parent === current) {
|
|
945
1113
|
break;
|
|
946
1114
|
}
|
|
@@ -1076,13 +1244,23 @@ async function buildUIOnly(detected, options) {
|
|
|
1076
1244
|
console.log(` Sourcemap: ${sourcemap ? "enabled" : "disabled"}`);
|
|
1077
1245
|
console.log("");
|
|
1078
1246
|
}
|
|
1247
|
+
let title;
|
|
1248
|
+
let description;
|
|
1249
|
+
try {
|
|
1250
|
+
const pkgPath = resolve3(detected.projectRoot, "package.json");
|
|
1251
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1252
|
+
title = pkg.vertz?.title ?? pkg.title;
|
|
1253
|
+
description = pkg.vertz?.description ?? pkg.description;
|
|
1254
|
+
} catch {}
|
|
1079
1255
|
const result = await buildUI({
|
|
1080
1256
|
projectRoot: detected.projectRoot,
|
|
1081
1257
|
clientEntry: detected.clientEntry,
|
|
1082
1258
|
serverEntry,
|
|
1083
1259
|
outputDir: "dist",
|
|
1084
1260
|
minify: !noMinify,
|
|
1085
|
-
sourcemap
|
|
1261
|
+
sourcemap,
|
|
1262
|
+
title,
|
|
1263
|
+
description
|
|
1086
1264
|
});
|
|
1087
1265
|
if (!result.success) {
|
|
1088
1266
|
return err(new Error(result.error));
|
|
@@ -1278,13 +1456,13 @@ async function dbBaselineAction(options) {
|
|
|
1278
1456
|
}
|
|
1279
1457
|
|
|
1280
1458
|
// src/commands/dev.ts
|
|
1281
|
-
import { join as
|
|
1459
|
+
import { join as join6 } from "node:path";
|
|
1282
1460
|
import { err as err5, ok as ok5 } from "@vertz/errors";
|
|
1283
1461
|
|
|
1284
1462
|
// src/dev-server/fullstack-server.ts
|
|
1285
1463
|
import { spawn } from "node:child_process";
|
|
1286
1464
|
import { existsSync as existsSync5 } from "node:fs";
|
|
1287
|
-
import { join as
|
|
1465
|
+
import { join as join5, relative } from "node:path";
|
|
1288
1466
|
|
|
1289
1467
|
// src/dev-server/process-manager.ts
|
|
1290
1468
|
function createProcessManager(spawnFn) {
|
|
@@ -1321,14 +1499,14 @@ function createProcessManager(spawnFn) {
|
|
|
1321
1499
|
return;
|
|
1322
1500
|
const proc = child;
|
|
1323
1501
|
child = undefined;
|
|
1324
|
-
await new Promise((
|
|
1502
|
+
await new Promise((resolve4) => {
|
|
1325
1503
|
const timeout = setTimeout(() => {
|
|
1326
1504
|
proc.kill("SIGKILL");
|
|
1327
|
-
|
|
1505
|
+
resolve4();
|
|
1328
1506
|
}, 2000);
|
|
1329
1507
|
proc.on("exit", () => {
|
|
1330
1508
|
clearTimeout(timeout);
|
|
1331
|
-
|
|
1509
|
+
resolve4();
|
|
1332
1510
|
});
|
|
1333
1511
|
proc.kill("SIGTERM");
|
|
1334
1512
|
});
|
|
@@ -1397,7 +1575,11 @@ Expected: export default createServer({ ... })`);
|
|
|
1397
1575
|
throw new Error(`${serverEntry} default export must have a .handler function.
|
|
1398
1576
|
Expected: export default createServer({ ... })`);
|
|
1399
1577
|
}
|
|
1400
|
-
|
|
1578
|
+
let sessionResolver;
|
|
1579
|
+
if ("auth" in mod && mod.auth && typeof mod.auth.resolveSessionForSSR === "function") {
|
|
1580
|
+
sessionResolver = mod.auth.resolveSessionForSSR;
|
|
1581
|
+
}
|
|
1582
|
+
return { ...mod, sessionResolver };
|
|
1401
1583
|
}
|
|
1402
1584
|
function formatBanner(appType, port, host) {
|
|
1403
1585
|
const url = `http://${host}:${port}`;
|
|
@@ -1418,19 +1600,22 @@ async function startDevServer(options) {
|
|
|
1418
1600
|
}
|
|
1419
1601
|
const { createBunDevServer } = await import("@vertz/ui-server/bun-dev-server");
|
|
1420
1602
|
let apiHandler;
|
|
1603
|
+
let sessionResolver;
|
|
1421
1604
|
if (mode.kind === "full-stack") {
|
|
1422
1605
|
const serverMod = await importServerModule(mode.serverEntry);
|
|
1423
1606
|
apiHandler = serverMod.handler;
|
|
1607
|
+
sessionResolver = serverMod.sessionResolver;
|
|
1424
1608
|
}
|
|
1425
1609
|
const uiEntry = `./${relative(detected.projectRoot, mode.uiEntry)}`;
|
|
1426
1610
|
const clientEntry = mode.clientEntry ? `/${relative(detected.projectRoot, mode.clientEntry)}` : undefined;
|
|
1427
|
-
const openapiPath =
|
|
1611
|
+
const openapiPath = join5(detected.projectRoot, ".vertz/generated/openapi.json");
|
|
1428
1612
|
const openapi = existsSync5(openapiPath) ? { specPath: openapiPath } : undefined;
|
|
1429
1613
|
const devServer = createBunDevServer({
|
|
1430
1614
|
entry: uiEntry,
|
|
1431
1615
|
port,
|
|
1432
1616
|
host,
|
|
1433
1617
|
apiHandler,
|
|
1618
|
+
sessionResolver,
|
|
1434
1619
|
openapi,
|
|
1435
1620
|
ssrModule: mode.ssrModule,
|
|
1436
1621
|
clientEntry,
|
|
@@ -1451,10 +1636,10 @@ function startApiOnlyServer(serverEntry, port) {
|
|
|
1451
1636
|
stdio: "inherit"
|
|
1452
1637
|
}));
|
|
1453
1638
|
pm.start(serverEntry, { PORT: String(port) });
|
|
1454
|
-
return new Promise((
|
|
1639
|
+
return new Promise((resolve4) => {
|
|
1455
1640
|
const shutdown = async () => {
|
|
1456
1641
|
await pm.stop();
|
|
1457
|
-
|
|
1642
|
+
resolve4();
|
|
1458
1643
|
};
|
|
1459
1644
|
process.on("SIGINT", shutdown);
|
|
1460
1645
|
process.on("SIGTERM", shutdown);
|
|
@@ -1528,7 +1713,7 @@ async function devAction(options = {}) {
|
|
|
1528
1713
|
}
|
|
1529
1714
|
}
|
|
1530
1715
|
const _watcher = createPipelineWatcher({
|
|
1531
|
-
dir:
|
|
1716
|
+
dir: join6(projectRoot, "src"),
|
|
1532
1717
|
debounceMs: 100,
|
|
1533
1718
|
onChange: async (changes) => {
|
|
1534
1719
|
if (!isRunning)
|
|
@@ -1650,34 +1835,35 @@ function generateAction(options) {
|
|
|
1650
1835
|
}
|
|
1651
1836
|
|
|
1652
1837
|
// src/commands/start.ts
|
|
1653
|
-
import { existsSync as existsSync6, readdirSync as
|
|
1654
|
-
import { join as
|
|
1838
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
1839
|
+
import { join as join7, resolve as resolve4 } from "node:path";
|
|
1655
1840
|
import { err as err7, ok as ok7 } from "@vertz/errors";
|
|
1656
1841
|
function discoverSSRModule(projectRoot) {
|
|
1657
|
-
const serverDir =
|
|
1842
|
+
const serverDir = join7(projectRoot, "dist", "server");
|
|
1658
1843
|
if (!existsSync6(serverDir))
|
|
1659
1844
|
return;
|
|
1660
|
-
const files =
|
|
1845
|
+
const files = readdirSync3(serverDir).filter((f) => f.endsWith(".js"));
|
|
1661
1846
|
if (files.length === 0)
|
|
1662
1847
|
return;
|
|
1663
1848
|
if (files.includes("app.js")) {
|
|
1664
|
-
return
|
|
1849
|
+
return join7(serverDir, "app.js");
|
|
1665
1850
|
}
|
|
1666
1851
|
const first = files[0];
|
|
1667
|
-
return first ?
|
|
1852
|
+
return first ? join7(serverDir, first) : undefined;
|
|
1668
1853
|
}
|
|
1669
1854
|
function validateBuildOutputs(projectRoot, appType) {
|
|
1670
1855
|
const missing = [];
|
|
1671
1856
|
if (appType === "api-only" || appType === "full-stack") {
|
|
1672
|
-
const apiBuild =
|
|
1857
|
+
const apiBuild = join7(projectRoot, ".vertz", "build", "index.js");
|
|
1673
1858
|
if (!existsSync6(apiBuild)) {
|
|
1674
1859
|
missing.push(".vertz/build/index.js");
|
|
1675
1860
|
}
|
|
1676
1861
|
}
|
|
1677
1862
|
if (appType === "ui-only" || appType === "full-stack") {
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1680
|
-
|
|
1863
|
+
const shellHtml = join7(projectRoot, "dist", "client", "_shell.html");
|
|
1864
|
+
const clientHtml = join7(projectRoot, "dist", "client", "index.html");
|
|
1865
|
+
if (!existsSync6(shellHtml) && !existsSync6(clientHtml)) {
|
|
1866
|
+
missing.push("dist/client/_shell.html");
|
|
1681
1867
|
}
|
|
1682
1868
|
const ssrModule = discoverSSRModule(projectRoot);
|
|
1683
1869
|
if (!ssrModule) {
|
|
@@ -1722,7 +1908,7 @@ async function startAction(options = {}) {
|
|
|
1722
1908
|
}
|
|
1723
1909
|
}
|
|
1724
1910
|
async function startApiOnly(projectRoot, port, host, _verbose) {
|
|
1725
|
-
const entryPath =
|
|
1911
|
+
const entryPath = resolve4(projectRoot, ".vertz", "build", "index.js");
|
|
1726
1912
|
let mod;
|
|
1727
1913
|
try {
|
|
1728
1914
|
mod = await import(entryPath);
|
|
@@ -1747,8 +1933,10 @@ async function startUIOnly(projectRoot, port, host, _verbose) {
|
|
|
1747
1933
|
if (!ssrModulePath) {
|
|
1748
1934
|
return err7(new Error('No SSR module found in dist/server/. Run "vertz build" first.'));
|
|
1749
1935
|
}
|
|
1750
|
-
const
|
|
1751
|
-
const
|
|
1936
|
+
const shellPath = resolve4(projectRoot, "dist", "client", "_shell.html");
|
|
1937
|
+
const legacyPath = resolve4(projectRoot, "dist", "client", "index.html");
|
|
1938
|
+
const templatePath = existsSync6(shellPath) ? shellPath : legacyPath;
|
|
1939
|
+
const template = readFileSync3(templatePath, "utf-8");
|
|
1752
1940
|
let ssrModule;
|
|
1753
1941
|
try {
|
|
1754
1942
|
ssrModule = await import(ssrModulePath);
|
|
@@ -1762,16 +1950,22 @@ async function startUIOnly(projectRoot, port, host, _verbose) {
|
|
|
1762
1950
|
template,
|
|
1763
1951
|
inlineCSS
|
|
1764
1952
|
});
|
|
1765
|
-
const clientDir =
|
|
1953
|
+
const clientDir = resolve4(projectRoot, "dist", "client");
|
|
1766
1954
|
const server = Bun.serve({
|
|
1767
1955
|
port,
|
|
1768
1956
|
hostname: host,
|
|
1769
1957
|
async fetch(req) {
|
|
1770
1958
|
const url = new URL(req.url);
|
|
1771
1959
|
const pathname = url.pathname;
|
|
1960
|
+
if (req.headers.get("x-vertz-nav") === "1") {
|
|
1961
|
+
return ssrHandler(req);
|
|
1962
|
+
}
|
|
1772
1963
|
const staticResponse = serveStaticFile(clientDir, pathname);
|
|
1773
1964
|
if (staticResponse)
|
|
1774
1965
|
return staticResponse;
|
|
1966
|
+
const prerenderResponse = servePrerenderHTML(clientDir, pathname);
|
|
1967
|
+
if (prerenderResponse)
|
|
1968
|
+
return prerenderResponse;
|
|
1775
1969
|
return ssrHandler(req);
|
|
1776
1970
|
}
|
|
1777
1971
|
});
|
|
@@ -1780,7 +1974,7 @@ async function startUIOnly(projectRoot, port, host, _verbose) {
|
|
|
1780
1974
|
return ok7(undefined);
|
|
1781
1975
|
}
|
|
1782
1976
|
async function startFullStack(projectRoot, port, host, _verbose) {
|
|
1783
|
-
const apiEntryPath =
|
|
1977
|
+
const apiEntryPath = resolve4(projectRoot, ".vertz", "build", "index.js");
|
|
1784
1978
|
let apiMod;
|
|
1785
1979
|
try {
|
|
1786
1980
|
apiMod = await import(apiEntryPath);
|
|
@@ -1795,8 +1989,10 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1795
1989
|
if (!ssrModulePath) {
|
|
1796
1990
|
return err7(new Error('No SSR module found in dist/server/. Run "vertz build" first.'));
|
|
1797
1991
|
}
|
|
1798
|
-
const
|
|
1799
|
-
const
|
|
1992
|
+
const shellPath = resolve4(projectRoot, "dist", "client", "_shell.html");
|
|
1993
|
+
const legacyPath = resolve4(projectRoot, "dist", "client", "index.html");
|
|
1994
|
+
const templatePath = existsSync6(shellPath) ? shellPath : legacyPath;
|
|
1995
|
+
const template = readFileSync3(templatePath, "utf-8");
|
|
1800
1996
|
let ssrModule;
|
|
1801
1997
|
try {
|
|
1802
1998
|
ssrModule = await import(ssrModulePath);
|
|
@@ -1810,7 +2006,7 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1810
2006
|
template,
|
|
1811
2007
|
inlineCSS
|
|
1812
2008
|
});
|
|
1813
|
-
const clientDir =
|
|
2009
|
+
const clientDir = resolve4(projectRoot, "dist", "client");
|
|
1814
2010
|
const server = Bun.serve({
|
|
1815
2011
|
port,
|
|
1816
2012
|
hostname: host,
|
|
@@ -1820,9 +2016,15 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1820
2016
|
if (pathname.startsWith("/api")) {
|
|
1821
2017
|
return apiHandler(req);
|
|
1822
2018
|
}
|
|
2019
|
+
if (req.headers.get("x-vertz-nav") === "1") {
|
|
2020
|
+
return ssrHandler(req);
|
|
2021
|
+
}
|
|
1823
2022
|
const staticResponse = serveStaticFile(clientDir, pathname);
|
|
1824
2023
|
if (staticResponse)
|
|
1825
2024
|
return staticResponse;
|
|
2025
|
+
const prerenderResponse = servePrerenderHTML(clientDir, pathname);
|
|
2026
|
+
if (prerenderResponse)
|
|
2027
|
+
return prerenderResponse;
|
|
1826
2028
|
return ssrHandler(req);
|
|
1827
2029
|
}
|
|
1828
2030
|
});
|
|
@@ -1831,25 +2033,43 @@ async function startFullStack(projectRoot, port, host, _verbose) {
|
|
|
1831
2033
|
return ok7(undefined);
|
|
1832
2034
|
}
|
|
1833
2035
|
function discoverInlineCSS(projectRoot) {
|
|
1834
|
-
const cssDir =
|
|
2036
|
+
const cssDir = resolve4(projectRoot, "dist", "client", "assets");
|
|
1835
2037
|
if (!existsSync6(cssDir))
|
|
1836
2038
|
return;
|
|
1837
|
-
const cssFiles =
|
|
2039
|
+
const cssFiles = readdirSync3(cssDir).filter((f) => f.endsWith(".css"));
|
|
1838
2040
|
if (cssFiles.length === 0)
|
|
1839
2041
|
return;
|
|
1840
2042
|
const result = {};
|
|
1841
2043
|
for (const file of cssFiles) {
|
|
1842
|
-
const content =
|
|
2044
|
+
const content = readFileSync3(join7(cssDir, file), "utf-8");
|
|
1843
2045
|
result[`/assets/${file}`] = content;
|
|
1844
2046
|
}
|
|
1845
2047
|
return result;
|
|
1846
2048
|
}
|
|
2049
|
+
function servePrerenderHTML(clientDir, pathname) {
|
|
2050
|
+
const htmlPath = pathname === "/" ? resolve4(clientDir, "index.html") : resolve4(clientDir, `${pathname.replace(/^\//, "")}/index.html`);
|
|
2051
|
+
if (!htmlPath.startsWith(clientDir))
|
|
2052
|
+
return null;
|
|
2053
|
+
if (htmlPath.endsWith("/_shell.html"))
|
|
2054
|
+
return null;
|
|
2055
|
+
const file = Bun.file(htmlPath);
|
|
2056
|
+
if (!file.size)
|
|
2057
|
+
return null;
|
|
2058
|
+
return new Response(file, {
|
|
2059
|
+
headers: {
|
|
2060
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
2061
|
+
"Cache-Control": "public, max-age=0, must-revalidate"
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
1847
2065
|
function serveStaticFile(clientDir, pathname) {
|
|
1848
2066
|
if (pathname === "/" || pathname === "/index.html")
|
|
1849
2067
|
return null;
|
|
1850
|
-
const filePath =
|
|
2068
|
+
const filePath = resolve4(clientDir, `.${pathname}`);
|
|
1851
2069
|
if (!filePath.startsWith(clientDir))
|
|
1852
2070
|
return null;
|
|
2071
|
+
if (!existsSync6(filePath) || !statSync2(filePath).isFile())
|
|
2072
|
+
return null;
|
|
1853
2073
|
const file = Bun.file(filePath);
|
|
1854
2074
|
if (!file.size)
|
|
1855
2075
|
return null;
|
|
@@ -1944,12 +2164,12 @@ function createCLI() {
|
|
|
1944
2164
|
}
|
|
1945
2165
|
});
|
|
1946
2166
|
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) => {
|
|
1947
|
-
const { resolve:
|
|
2167
|
+
const { resolve: resolve5, dirname: dirname3 } = await import("node:path");
|
|
1948
2168
|
const { mkdir, writeFile: fsWriteFile } = await import("node:fs/promises");
|
|
1949
2169
|
let config;
|
|
1950
2170
|
let compilerConfig;
|
|
1951
2171
|
try {
|
|
1952
|
-
const configPath =
|
|
2172
|
+
const configPath = resolve5(process.cwd(), "vertz.config.ts");
|
|
1953
2173
|
const jiti = createJiti(import.meta.url);
|
|
1954
2174
|
const configModule = await jiti.import(configPath);
|
|
1955
2175
|
config = configModule.codegen ?? configModule.default?.codegen;
|
|
@@ -1974,7 +2194,7 @@ function createCLI() {
|
|
|
1974
2194
|
const { createCodegenPipeline: createCodegenPipeline2 } = await import("@vertz/codegen");
|
|
1975
2195
|
const pipeline = createCodegenPipeline2();
|
|
1976
2196
|
const writeFile = async (path, content) => {
|
|
1977
|
-
await mkdir(
|
|
2197
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
1978
2198
|
await fsWriteFile(path, content, "utf-8");
|
|
1979
2199
|
};
|
|
1980
2200
|
const result = await codegenAction({
|
|
@@ -2075,8 +2295,8 @@ Schema is in sync.`);
|
|
|
2075
2295
|
if (!opts.force) {
|
|
2076
2296
|
const readline = await import("node:readline");
|
|
2077
2297
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2078
|
-
const answer = await new Promise((
|
|
2079
|
-
rl.question("This will drop all tables and re-apply migrations. Continue? (y/N) ",
|
|
2298
|
+
const answer = await new Promise((resolve5) => {
|
|
2299
|
+
rl.question("This will drop all tables and re-apply migrations. Continue? (y/N) ", resolve5);
|
|
2080
2300
|
});
|
|
2081
2301
|
rl.close();
|
|
2082
2302
|
if (answer.toLowerCase() !== "y") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Vertz CLI — dev, build, create, and deploy commands",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@vertz/codegen": "^0.2.
|
|
38
|
-
"@vertz/errors": "^0.2.
|
|
39
|
-
"@vertz/tui": "^0.2.
|
|
40
|
-
"@vertz/compiler": "^0.2.
|
|
41
|
-
"@vertz/create-vertz-app": "^0.2.
|
|
42
|
-
"@vertz/db": "^0.2.
|
|
43
|
-
"@vertz/ui-server": "^0.2.
|
|
37
|
+
"@vertz/codegen": "^0.2.16",
|
|
38
|
+
"@vertz/errors": "^0.2.16",
|
|
39
|
+
"@vertz/tui": "^0.2.16",
|
|
40
|
+
"@vertz/compiler": "^0.2.16",
|
|
41
|
+
"@vertz/create-vertz-app": "^0.2.16",
|
|
42
|
+
"@vertz/db": "^0.2.16",
|
|
43
|
+
"@vertz/ui-server": "^0.2.16",
|
|
44
44
|
"esbuild": "^0.25.0",
|
|
45
45
|
"commander": "^14.0.0",
|
|
46
46
|
"jiti": "^2.4.2"
|