@vistagenic/vista 0.3.6 → 0.3.8
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/bin/flashpack-agent-playground.mjs +134 -0
- package/bin/flashpack-boot.mjs +504 -0
- package/bin/flashpack-ssr-loader.mjs +42 -0
- package/bin/flashpack-ssr.mjs +81 -0
- package/bin/flashpack-vendor.mjs +97 -0
- package/bin/vista.js +56 -42
- package/dist/ai/providers/base.js +7 -1
- package/dist/bin/build-rsc.js +16 -0
- package/dist/bin/build.js +17 -11
- package/dist/bin/deploy.js +7 -0
- package/dist/bin/dev-client-runtime.d.ts +6 -0
- package/dist/bin/dev-client-runtime.js +99 -0
- package/dist/bin/dev-error-overlay-snippet.js +79 -21
- package/dist/bin/dev-error-overlay-snippet.ts +840 -0
- package/dist/bin/devtools-indicator-snippet.js +29 -13
- package/dist/bin/file-scanner.js +7 -24
- package/dist/bin/generate.js +94 -2
- package/dist/bin/webpack.config.js +3 -1
- package/dist/build/rsc/compiler.d.ts +14 -0
- package/dist/build/rsc/compiler.js +66 -6
- package/dist/build/rsc/server-manifest.js +5 -4
- package/dist/config.d.ts +5 -0
- package/dist/config.js +15 -1
- package/dist/deploy/adapters/render.js +2 -2
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +3 -0
- package/dist/deploy/runtime-pack.js +22 -1
- package/dist/deploy/utils.js +16 -6
- package/dist/flashpack/command.d.ts +1 -0
- package/dist/flashpack/command.js +42 -76
- package/dist/flashpack/runtime.d.ts +12 -0
- package/dist/flashpack/runtime.js +124 -22
- package/dist/metadata/generate.d.ts +1 -0
- package/dist/metadata/generate.js +117 -21
- package/dist/metadata/index.d.ts +5 -0
- package/dist/metadata/index.js +15 -1
- package/dist/metadata/json-ld.d.ts +15 -0
- package/dist/metadata/json-ld.js +19 -0
- package/dist/metadata/merge.d.ts +17 -0
- package/dist/metadata/merge.js +161 -0
- package/dist/metadata/routes.d.ts +66 -0
- package/dist/metadata/routes.js +143 -0
- package/dist/metadata/types.d.ts +1 -1
- package/dist/metadata/types.js +3 -1
- package/dist/og/image-response.d.ts +38 -0
- package/dist/og/image-response.js +88 -0
- package/dist/og/index.d.ts +2 -0
- package/dist/og/index.js +5 -0
- package/dist/rust/index.d.ts +13 -0
- package/dist/rust/index.js +132 -0
- package/dist/server/engine.js +13 -3
- package/dist/server/logger.js +2 -1
- package/dist/server/project-alias-resolver.d.ts +7 -0
- package/dist/server/project-alias-resolver.js +70 -0
- package/dist/server/rsc-engine.js +93 -16
- package/dist/server/static-generator.js +4 -3
- package/dist/server/typed-api-runtime.js +92 -0
- package/docs/ai.md +69 -0
- package/docs/app.md +30 -0
- package/docs/cli.md +29 -0
- package/docs/index.md +18 -0
- package/docs/rag.md +54 -0
- package/native/win32-x64/flashpack-cli.exe +0 -0
- package/package.json +196 -178
- package/skills/README.md +9 -0
- package/skills/vista-agent/SKILL.md +28 -0
- package/skills/vista-dev-loop/SKILL.md +25 -0
- package/skills/vista-rag/SKILL.md +26 -0
|
@@ -616,13 +616,29 @@ async function createRouteElement(route, context, isDev, rootLayout, runtimeRoot
|
|
|
616
616
|
clearProjectRequireCache(runtimeRoot);
|
|
617
617
|
}
|
|
618
618
|
const PageModule = require(route.pagePath);
|
|
619
|
-
|
|
619
|
+
const { deepMergeMetadata, mergeMetadataChain } = require('../metadata/merge');
|
|
620
|
+
const layoutMetadata = [];
|
|
621
|
+
for (const layoutPath of route.layoutPaths || []) {
|
|
622
|
+
try {
|
|
623
|
+
const layoutModule = require(layoutPath);
|
|
624
|
+
if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
|
|
625
|
+
layoutMetadata.push(layoutModule.metadata);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
catch {
|
|
629
|
+
// Ignore layout metadata load failures
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (layoutMetadata.length === 0 && rootLayout.metadata) {
|
|
633
|
+
layoutMetadata.push(rootLayout.metadata);
|
|
634
|
+
}
|
|
635
|
+
let metadata = mergeMetadataChain(layoutMetadata);
|
|
620
636
|
if (PageModule.metadata) {
|
|
621
|
-
metadata =
|
|
637
|
+
metadata = deepMergeMetadata(metadata, PageModule.metadata);
|
|
622
638
|
}
|
|
623
639
|
if (typeof PageModule.generateMetadata === 'function') {
|
|
624
640
|
const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
|
|
625
|
-
metadata =
|
|
641
|
+
metadata = deepMergeMetadata(metadata, dynamicMeta);
|
|
626
642
|
}
|
|
627
643
|
const element = await renderAppSubtreeElement({
|
|
628
644
|
subtreeRootDir: (0, app_dir_1.resolveAppDir)(runtimeRoot),
|
|
@@ -877,6 +893,18 @@ function wrapInDocumentShell(bodyContent, metadataHtml, chunkFiles, rootMode) {
|
|
|
877
893
|
</body>
|
|
878
894
|
</html>`;
|
|
879
895
|
}
|
|
896
|
+
function logWebpackBuildErrors(errors) {
|
|
897
|
+
const count = errors.length;
|
|
898
|
+
console.error('');
|
|
899
|
+
(0, logger_1.logError)(`Build failed with ${count} error${count === 1 ? '' : 's'}`);
|
|
900
|
+
for (const message of errors) {
|
|
901
|
+
const text = message.replace(/\u001b\[[0-9;]*m/g, '').trim();
|
|
902
|
+
if (!text)
|
|
903
|
+
continue;
|
|
904
|
+
console.error(text);
|
|
905
|
+
console.error('');
|
|
906
|
+
}
|
|
907
|
+
}
|
|
880
908
|
function normalizeWebpackErrors(stats) {
|
|
881
909
|
const errors = stats.toJson().errors || [];
|
|
882
910
|
const normalizedErrors = errors
|
|
@@ -1137,8 +1165,26 @@ function startRSCServer(options = {}) {
|
|
|
1137
1165
|
// - Pushes compile errors/success from webpack client build
|
|
1138
1166
|
// ========================================================================
|
|
1139
1167
|
const sseReloadClients = new Set();
|
|
1168
|
+
// Cold start only: block until the first successful client compile.
|
|
1169
|
+
// After that, rebuilds must not flash a full-page "compiling" interstitial
|
|
1170
|
+
// (Next does not either — HMR/reload waits for the rebuild to finish).
|
|
1140
1171
|
let clientCompileState = isDev && options.compiler ? 'compiling' : 'ready';
|
|
1141
1172
|
let clientCompileErrors = [];
|
|
1173
|
+
let clientBundleReadyOnce = !(isDev && options.compiler);
|
|
1174
|
+
let clientRebuildInFlight = false;
|
|
1175
|
+
let pendingLiveReload = false;
|
|
1176
|
+
const pushSSE = (payload) => {
|
|
1177
|
+
sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
|
|
1178
|
+
};
|
|
1179
|
+
const flushPendingLiveReload = () => {
|
|
1180
|
+
if (!pendingLiveReload)
|
|
1181
|
+
return;
|
|
1182
|
+
pendingLiveReload = false;
|
|
1183
|
+
(0, logger_1.logEvent)('Source changed, reloading...');
|
|
1184
|
+
pushSSE('reload');
|
|
1185
|
+
};
|
|
1186
|
+
/** True only while we still have no usable client/SSR bundle to serve. */
|
|
1187
|
+
const shouldBlockForClientCompile = () => Boolean(isDev && options.compiler && !clientBundleReadyOnce && clientCompileState === 'compiling');
|
|
1142
1188
|
if (isDev) {
|
|
1143
1189
|
app.get(constants_1.SSE_ENDPOINT, (req, res) => {
|
|
1144
1190
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
@@ -1151,9 +1197,6 @@ function startRSCServer(options = {}) {
|
|
|
1151
1197
|
sseReloadClients.delete(res);
|
|
1152
1198
|
});
|
|
1153
1199
|
});
|
|
1154
|
-
const pushSSE = (payload) => {
|
|
1155
|
-
sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
|
|
1156
|
-
};
|
|
1157
1200
|
const watchExtPattern = /\.(?:[cm]?[jt]sx?|css|md|mdx|json)$/i;
|
|
1158
1201
|
const watchRoots = [
|
|
1159
1202
|
'app',
|
|
@@ -1171,6 +1214,12 @@ function startRSCServer(options = {}) {
|
|
|
1171
1214
|
.filter((entry) => fs_1.default.existsSync(entry));
|
|
1172
1215
|
let reloadTimer = null;
|
|
1173
1216
|
const scheduleReload = () => {
|
|
1217
|
+
// If webpack is mid-rebuild, wait for `done` so the browser does not
|
|
1218
|
+
// reload into a compiling gate (the intermittent Next-unlike flash).
|
|
1219
|
+
if (options.compiler && (clientRebuildInFlight || !clientBundleReadyOnce)) {
|
|
1220
|
+
pendingLiveReload = true;
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1174
1223
|
if (reloadTimer)
|
|
1175
1224
|
clearTimeout(reloadTimer);
|
|
1176
1225
|
reloadTimer = setTimeout(() => {
|
|
@@ -1229,28 +1278,36 @@ function startRSCServer(options = {}) {
|
|
|
1229
1278
|
}));
|
|
1230
1279
|
// No webpack-hot-middleware — Vista uses SSE live-reload for RSC
|
|
1231
1280
|
options.compiler.hooks.invalid.tap('VistaRSCCompileStateInvalid', () => {
|
|
1232
|
-
|
|
1281
|
+
clientRebuildInFlight = true;
|
|
1233
1282
|
clientCompileErrors = [];
|
|
1283
|
+
// Only gate requests on the cold first compile — not every HMR rebuild.
|
|
1284
|
+
if (!clientBundleReadyOnce) {
|
|
1285
|
+
clientCompileState = 'compiling';
|
|
1286
|
+
}
|
|
1234
1287
|
});
|
|
1235
1288
|
// Push compile errors/success to SSE clients
|
|
1236
1289
|
options.compiler.hooks.done.tap('VistaRSCLiveReload', (stats) => {
|
|
1290
|
+
clientRebuildInFlight = false;
|
|
1237
1291
|
if (stats.hasErrors()) {
|
|
1238
1292
|
const normalizedErrors = normalizeWebpackErrors(stats);
|
|
1239
1293
|
const fallback = normalizedErrors.join('\n\n');
|
|
1240
1294
|
clientCompileState = 'error';
|
|
1241
1295
|
clientCompileErrors = normalizedErrors;
|
|
1296
|
+
logWebpackBuildErrors(normalizedErrors);
|
|
1242
1297
|
const payload = JSON.stringify({
|
|
1243
1298
|
type: 'error',
|
|
1244
1299
|
message: fallback,
|
|
1245
1300
|
errors: normalizedErrors,
|
|
1246
1301
|
});
|
|
1247
|
-
|
|
1302
|
+
pushSSE(payload);
|
|
1248
1303
|
}
|
|
1249
1304
|
else {
|
|
1250
1305
|
clientCompileState = 'ready';
|
|
1251
1306
|
clientCompileErrors = [];
|
|
1307
|
+
clientBundleReadyOnce = true;
|
|
1252
1308
|
const payload = JSON.stringify({ type: 'ok' });
|
|
1253
|
-
|
|
1309
|
+
pushSSE(payload);
|
|
1310
|
+
flushPendingLiveReload();
|
|
1254
1311
|
}
|
|
1255
1312
|
});
|
|
1256
1313
|
options.compiler.hooks.afterEmit.tap('VistaRSCServerManifestReload', () => {
|
|
@@ -1341,13 +1398,17 @@ function startRSCServer(options = {}) {
|
|
|
1341
1398
|
const imageHandler = (0, image_optimizer_1.createImageHandler)(runtimeRoot, isDev);
|
|
1342
1399
|
app.get(constants_1.IMAGE_ENDPOINT, imageHandler);
|
|
1343
1400
|
app.use(express_1.default.static(path_1.default.join(runtimeRoot, 'public')));
|
|
1401
|
+
// Also serve the project public/ (dev + apps whose runtimeRoot is a standalone copy).
|
|
1402
|
+
if (path_1.default.resolve(runtimeRoot) !== path_1.default.resolve(cwd)) {
|
|
1403
|
+
app.use(express_1.default.static(path_1.default.join(cwd, 'public')));
|
|
1404
|
+
}
|
|
1344
1405
|
app.use(`${constants_1.URL_PREFIX}/static`, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR, 'static')));
|
|
1345
1406
|
app.use(constants_1.URL_PREFIX, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
|
|
1346
1407
|
app.use(express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
|
|
1347
1408
|
const getUpstreamUnavailableMessage = () => `RSC upstream unavailable (${upstreamOrigin}/rsc): ${upstreamUnavailableReason || 'upstream process is not running'}`;
|
|
1348
1409
|
const proxyRSCRequest = async (req, res) => {
|
|
1349
1410
|
if (isDev && options.compiler) {
|
|
1350
|
-
if (
|
|
1411
|
+
if (shouldBlockForClientCompile()) {
|
|
1351
1412
|
res.status(503).type('text/plain').send('[vista] Client bundle is compiling. Retry shortly.');
|
|
1352
1413
|
return;
|
|
1353
1414
|
}
|
|
@@ -1457,7 +1518,7 @@ function startRSCServer(options = {}) {
|
|
|
1457
1518
|
return;
|
|
1458
1519
|
}
|
|
1459
1520
|
if (isDev && options.compiler) {
|
|
1460
|
-
if (
|
|
1521
|
+
if (shouldBlockForClientCompile()) {
|
|
1461
1522
|
res.status(503).type('text/html').send(renderCompilePendingHTML());
|
|
1462
1523
|
return;
|
|
1463
1524
|
}
|
|
@@ -1556,8 +1617,8 @@ function startRSCServer(options = {}) {
|
|
|
1556
1617
|
}
|
|
1557
1618
|
}
|
|
1558
1619
|
if (!useFlightSSR || !flightSSRClient || !isSSRManifestReady(ssrManifest)) {
|
|
1559
|
-
if (isDev && options.compiler) {
|
|
1560
|
-
//
|
|
1620
|
+
if (isDev && options.compiler && !clientBundleReadyOnce) {
|
|
1621
|
+
// Cold start only — wait for the first webpack emit / SSR manifest.
|
|
1561
1622
|
res.status(503).type('text/html').send(renderCompilePendingHTML());
|
|
1562
1623
|
return;
|
|
1563
1624
|
}
|
|
@@ -1589,15 +1650,31 @@ function startRSCServer(options = {}) {
|
|
|
1589
1650
|
clearProjectRequireCache(runtimeRoot);
|
|
1590
1651
|
}
|
|
1591
1652
|
const PageModule = require(route.pagePath);
|
|
1592
|
-
|
|
1653
|
+
const { deepMergeMetadata, mergeMetadataChain } = require('../metadata/merge');
|
|
1654
|
+
const layoutMetadata = [];
|
|
1655
|
+
for (const layoutPath of route.layoutPaths || []) {
|
|
1656
|
+
try {
|
|
1657
|
+
const layoutModule = require(layoutPath);
|
|
1658
|
+
if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
|
|
1659
|
+
layoutMetadata.push(layoutModule.metadata);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
catch {
|
|
1663
|
+
// Ignore layout metadata load failures
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
if (layoutMetadata.length === 0 && rootLayout.metadata) {
|
|
1667
|
+
layoutMetadata.push(rootLayout.metadata);
|
|
1668
|
+
}
|
|
1669
|
+
let metadata = mergeMetadataChain(layoutMetadata);
|
|
1593
1670
|
if (PageModule.metadata) {
|
|
1594
|
-
metadata =
|
|
1671
|
+
metadata = deepMergeMetadata(metadata, PageModule.metadata);
|
|
1595
1672
|
}
|
|
1596
1673
|
if (typeof PageModule.generateMetadata === 'function') {
|
|
1597
1674
|
const params = extractParams(req.path, route);
|
|
1598
1675
|
const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
|
|
1599
1676
|
const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
|
|
1600
|
-
metadata =
|
|
1677
|
+
metadata = deepMergeMetadata(metadata, dynamicMeta);
|
|
1601
1678
|
}
|
|
1602
1679
|
const { generateMetadataHtml } = require('../metadata/generate');
|
|
1603
1680
|
metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
|
|
@@ -322,11 +322,12 @@ async function prerenderPage(urlPath, route, params, cwd, vistaDirRoot, appPprEn
|
|
|
322
322
|
}
|
|
323
323
|
let metadata = {};
|
|
324
324
|
const searchParams = {};
|
|
325
|
+
const { deepMergeMetadata } = require('../metadata/merge');
|
|
325
326
|
for (const layoutPath of route.layoutPaths) {
|
|
326
327
|
try {
|
|
327
328
|
const layoutModule = require(layoutPath);
|
|
328
329
|
if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
|
|
329
|
-
metadata =
|
|
330
|
+
metadata = deepMergeMetadata(metadata, layoutModule.metadata);
|
|
330
331
|
}
|
|
331
332
|
}
|
|
332
333
|
catch {
|
|
@@ -334,13 +335,13 @@ async function prerenderPage(urlPath, route, params, cwd, vistaDirRoot, appPprEn
|
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
if (pageModule.metadata && typeof pageModule.metadata === 'object') {
|
|
337
|
-
metadata =
|
|
338
|
+
metadata = deepMergeMetadata(metadata, pageModule.metadata);
|
|
338
339
|
}
|
|
339
340
|
if (typeof pageModule.generateMetadata === 'function') {
|
|
340
341
|
try {
|
|
341
342
|
const dynamicMeta = await pageModule.generateMetadata({ params: params || {}, searchParams }, metadata);
|
|
342
343
|
if (dynamicMeta && typeof dynamicMeta === 'object') {
|
|
343
|
-
metadata =
|
|
344
|
+
metadata = deepMergeMetadata(metadata, dynamicMeta);
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
347
|
catch (metadataError) {
|
|
@@ -33,7 +33,9 @@ const METADATA_ROUTE_MAPPINGS = [
|
|
|
33
33
|
{ requestPath: '/sitemap.xml', stem: 'sitemap' },
|
|
34
34
|
{ requestPath: '/manifest.webmanifest', stem: 'manifest' },
|
|
35
35
|
];
|
|
36
|
+
const IMAGE_ROUTE_STEMS = new Set(['opengraph-image', 'twitter-image']);
|
|
36
37
|
const ROUTE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
38
|
+
const STATIC_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp'];
|
|
37
39
|
class BodyLimitError extends Error {
|
|
38
40
|
status = 413;
|
|
39
41
|
constructor(limitBytes) {
|
|
@@ -136,6 +138,42 @@ function resolveMetadataRoutePath(cwd, stem) {
|
|
|
136
138
|
};
|
|
137
139
|
return searchGroupDirectories(appDir);
|
|
138
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve `opengraph-image` / `twitter-image` conventions (Next parity).
|
|
143
|
+
* `/docs/opengraph-image` → `app/docs/opengraph-image.tsx` (or static `.png`).
|
|
144
|
+
*/
|
|
145
|
+
function resolveImageMetadataRoutePath(cwd, requestPath) {
|
|
146
|
+
const pathname = String(requestPath || '').split('?')[0];
|
|
147
|
+
const match = pathname.match(/^(.*)\/(opengraph-image|twitter-image)(?:\.[a-z0-9]+)?$/i);
|
|
148
|
+
if (!match)
|
|
149
|
+
return null;
|
|
150
|
+
const prefix = (match[1] || '').replace(/\/+$/, '');
|
|
151
|
+
const stem = match[2].toLowerCase();
|
|
152
|
+
if (!IMAGE_ROUTE_STEMS.has(stem))
|
|
153
|
+
return null;
|
|
154
|
+
const appDir = (0, app_dir_1.resolveAppDir)(cwd);
|
|
155
|
+
const dir = prefix ? path_1.default.join(appDir, prefix.replace(/^\//, '')) : appDir;
|
|
156
|
+
for (const extension of ROUTE_FILE_EXTENSIONS) {
|
|
157
|
+
const candidate = path_1.default.join(dir, `${stem}${extension}`);
|
|
158
|
+
if (fs_1.default.existsSync(candidate))
|
|
159
|
+
return candidate;
|
|
160
|
+
}
|
|
161
|
+
for (const extension of STATIC_IMAGE_EXTENSIONS) {
|
|
162
|
+
const candidate = path_1.default.join(dir, `${stem}${extension}`);
|
|
163
|
+
if (fs_1.default.existsSync(candidate))
|
|
164
|
+
return candidate;
|
|
165
|
+
}
|
|
166
|
+
// Also allow route-group nesting: app/(marketing)/opengraph-image.tsx for /
|
|
167
|
+
if (!prefix) {
|
|
168
|
+
return resolveMetadataRoutePath(cwd, stem);
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
function getMetadataStemForRequest(requestPath) {
|
|
173
|
+
const pathname = String(requestPath || '').split('?')[0];
|
|
174
|
+
const mapping = METADATA_ROUTE_MAPPINGS.find((entry) => entry.requestPath === pathname);
|
|
175
|
+
return mapping?.stem ?? null;
|
|
176
|
+
}
|
|
139
177
|
function hasMethodMatch(router, pathname, method) {
|
|
140
178
|
const normalized = method.toLowerCase();
|
|
141
179
|
return router.resolve(pathname, normalized) !== null;
|
|
@@ -456,6 +494,10 @@ function resolveLegacyRouteHandlerPath(cwd, requestPath) {
|
|
|
456
494
|
routeCandidates.push(resolvedMetadataPath);
|
|
457
495
|
}
|
|
458
496
|
}
|
|
497
|
+
const imageRoutePath = resolveImageMetadataRoutePath(cwd, requestPath);
|
|
498
|
+
if (imageRoutePath) {
|
|
499
|
+
routeCandidates.push(imageRoutePath);
|
|
500
|
+
}
|
|
459
501
|
if (normalized.startsWith('api/')) {
|
|
460
502
|
const apiRoute = normalized.slice('api/'.length);
|
|
461
503
|
routeCandidates.push(path_1.default.join(appDir, 'api', apiRoute, 'route.ts'), path_1.default.join(appDir, 'api', apiRoute, 'route.tsx'), path_1.default.join(appDir, 'api', apiRoute, 'route.js'), path_1.default.join(appDir, 'api', apiRoute, 'route.jsx'), path_1.default.join(appDir, 'api', `${apiRoute}.ts`), path_1.default.join(appDir, 'api', `${apiRoute}.tsx`), path_1.default.join(appDir, 'api', `${apiRoute}.js`), path_1.default.join(appDir, 'api', `${apiRoute}.jsx`));
|
|
@@ -475,6 +517,23 @@ function getExportedRouteMethods(apiModule) {
|
|
|
475
517
|
async function runLegacyApiRoute(options) {
|
|
476
518
|
const { req, res, apiPath, isDev } = options;
|
|
477
519
|
const params = options.params || {};
|
|
520
|
+
const requestPath = String(req.path || req.url || '').split('?')[0];
|
|
521
|
+
const fileExt = path_1.default.extname(apiPath).toLowerCase();
|
|
522
|
+
// Static opengraph-image.png / twitter-image.jpg convention files.
|
|
523
|
+
if (STATIC_IMAGE_EXTENSIONS.includes(fileExt)) {
|
|
524
|
+
const contentTypes = {
|
|
525
|
+
'.png': 'image/png',
|
|
526
|
+
'.jpg': 'image/jpeg',
|
|
527
|
+
'.jpeg': 'image/jpeg',
|
|
528
|
+
'.gif': 'image/gif',
|
|
529
|
+
'.webp': 'image/webp',
|
|
530
|
+
};
|
|
531
|
+
res.status(200);
|
|
532
|
+
res.setHeader('Content-Type', contentTypes[fileExt] || 'application/octet-stream');
|
|
533
|
+
res.setHeader('Cache-Control', 'public, max-age=0, s-maxage=3600');
|
|
534
|
+
fs_1.default.createReadStream(apiPath).pipe(res);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
478
537
|
if (isDev) {
|
|
479
538
|
delete require.cache[require.resolve(apiPath)];
|
|
480
539
|
}
|
|
@@ -506,6 +565,16 @@ async function runLegacyApiRoute(options) {
|
|
|
506
565
|
await sendFetchResponse(res, result);
|
|
507
566
|
return;
|
|
508
567
|
}
|
|
568
|
+
const metadataStem = getMetadataStemForRequest(requestPath);
|
|
569
|
+
if (metadataStem && result !== undefined) {
|
|
570
|
+
const { metadataRouteToResponse } = require('../metadata/routes');
|
|
571
|
+
const converted = metadataRouteToResponse(metadataStem, result);
|
|
572
|
+
if (converted) {
|
|
573
|
+
res.req = res.req || req;
|
|
574
|
+
await sendFetchResponse(res, converted);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
509
578
|
if (result !== undefined) {
|
|
510
579
|
res.status(200).json(result);
|
|
511
580
|
return;
|
|
@@ -521,6 +590,29 @@ async function runLegacyApiRoute(options) {
|
|
|
521
590
|
throw error;
|
|
522
591
|
}
|
|
523
592
|
}
|
|
593
|
+
// MetadataRoute + OG image conventions: default export returning data or Response.
|
|
594
|
+
const metadataStem = getMetadataStemForRequest(requestPath);
|
|
595
|
+
const isImageMetadataRoute = /(?:^|\/)(opengraph-image|twitter-image)(?:\.[a-z0-9]+)?$/i.test(requestPath);
|
|
596
|
+
if ((metadataStem || isImageMetadataRoute) &&
|
|
597
|
+
typeof apiModule.default === 'function' &&
|
|
598
|
+
(method === 'GET' || method === 'HEAD')) {
|
|
599
|
+
try {
|
|
600
|
+
const result = await apiModule.default({ params });
|
|
601
|
+
let response = result instanceof Response ? result : null;
|
|
602
|
+
if (!response && metadataStem) {
|
|
603
|
+
const { metadataRouteToResponse } = require('../metadata/routes');
|
|
604
|
+
response = metadataRouteToResponse(metadataStem, result);
|
|
605
|
+
}
|
|
606
|
+
if (response) {
|
|
607
|
+
res.req = res.req || req;
|
|
608
|
+
await sendFetchResponse(res, response);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
catch (error) {
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
524
616
|
if (isEdgeRuntime(runtime) && typeof apiModule.default === 'function') {
|
|
525
617
|
res.status(500).json({
|
|
526
618
|
error: 'Edge runtime route handlers must export HTTP method functions instead of a default Express handler.',
|
package/docs/ai.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# AI
|
|
2
|
+
|
|
3
|
+
Import app code from `vista/ai` and React hooks from `vista/ai/react`. Do not add a third-party agent SDK for these primitives.
|
|
4
|
+
|
|
5
|
+
## Model strings
|
|
6
|
+
|
|
7
|
+
`provider:model`:
|
|
8
|
+
|
|
9
|
+
- `openai:gpt-4o` (`OPENAI_API_KEY`)
|
|
10
|
+
- `anthropic:claude-3-5-sonnet` (`ANTHROPIC_API_KEY`)
|
|
11
|
+
- `gemini:gemini-1.5-flash` (`GEMINI_API_KEY`)
|
|
12
|
+
- `ollama:llama3`
|
|
13
|
+
- `groq:llama-3.1-8b-instant` (`GROQ_API_KEY`)
|
|
14
|
+
- `nvidia:meta/llama-3.1-8b-instruct` (`NVIDIA_API_KEY` or `NIM_API_KEY`)
|
|
15
|
+
- `mock:echo` for tests with no key
|
|
16
|
+
|
|
17
|
+
Do not commit API keys or `.env` files.
|
|
18
|
+
|
|
19
|
+
## Agent
|
|
20
|
+
|
|
21
|
+
`vista g agent support` writes the files. The generated module looks like this:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { agent, tool } from 'vista/ai';
|
|
25
|
+
|
|
26
|
+
export const supportAgent = agent({
|
|
27
|
+
name: 'support',
|
|
28
|
+
model: process.env.VISTA_AI_MODEL || 'openai:gpt-4o',
|
|
29
|
+
systemPrompt: 'You are a helpful AI assistant for support.',
|
|
30
|
+
tools: [
|
|
31
|
+
tool({
|
|
32
|
+
name: 'ping',
|
|
33
|
+
description: 'Check agent connectivity',
|
|
34
|
+
execute: async () => ({ status: 'ok', time: new Date().toISOString() }),
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
memory: true,
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Route:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { supportAgent } from '../../../agents/support/agent';
|
|
45
|
+
|
|
46
|
+
export async function POST(req: Request) {
|
|
47
|
+
const { prompt, messages, sessionId } = await req.json();
|
|
48
|
+
const stream = supportAgent.stream({ prompt, messages, sessionId });
|
|
49
|
+
return stream.toDataStreamResponse();
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Client:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
'use client';
|
|
57
|
+
import { useAgent } from 'vista/ai/react';
|
|
58
|
+
|
|
59
|
+
export default function ChatPage() {
|
|
60
|
+
const { messages, input, setInput, handleSubmit, isLoading } = useAgent({
|
|
61
|
+
api: '/api/agents/support',
|
|
62
|
+
});
|
|
63
|
+
// render messages and a form that calls handleSubmit
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`agent()` options used by the framework: `name`, `model`, `systemPrompt`, `tools`, `memory`, `maxSteps` (default 5), `temperature`, `observability`. Turn another agent into a tool with `.asTool({ description })`.
|
|
68
|
+
|
|
69
|
+
`tool()` takes `name`, `description`, optional JSON-schema `parameters`, and `execute`.
|
package/docs/app.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# App conventions
|
|
2
|
+
|
|
3
|
+
Vista apps are React. Server Components are the default under `app/`. Add `'use client'` only on modules that use state, effects, or browser events.
|
|
4
|
+
|
|
5
|
+
## Routes
|
|
6
|
+
|
|
7
|
+
- `app/index.tsx` is the home page (`/`). There is no `app/page.tsx` convention.
|
|
8
|
+
- `app/root.tsx` is the root layout. There is no `app/layout.tsx` convention.
|
|
9
|
+
- A route segment is `app/<segment>/page.tsx` (for example `app/about/page.tsx` → `/about`).
|
|
10
|
+
- Dynamic segments use brackets: `app/docs/[...slug]/page.tsx`.
|
|
11
|
+
- HTTP handlers are `app/**/route.ts` exporting `GET`, `POST`, and the other methods.
|
|
12
|
+
- Shared UI is not scaffolded. Import theme pieces from `vista/theme` (`ThemeProvider`, `ThemeScript`, `ThemeToggle`). Add `components/` only when UI is reused.
|
|
13
|
+
|
|
14
|
+
## Client boundaries
|
|
15
|
+
|
|
16
|
+
`'use client'` may live in `app/`, `components/`, `utils/`, `lib/`, or `src/`. The client manifest scans those roots.
|
|
17
|
+
|
|
18
|
+
## Config and output
|
|
19
|
+
|
|
20
|
+
- `vista.config.ts` selects the engine with `engine.variant`: `'default'` or `'flashpack'`.
|
|
21
|
+
- `package.json` `vista.engine` is the same switch. Scripts stay `vista dev`, `vista build`, `vista start`.
|
|
22
|
+
- Production output is `.vista/` (the `.next` equivalent). Do not commit it.
|
|
23
|
+
- Flashpack dev output is `.flash/`. It is a compiler cache, not a second copy of `.vista/`.
|
|
24
|
+
- Dev server port is `3003`.
|
|
25
|
+
|
|
26
|
+
## SEO files already in a new app
|
|
27
|
+
|
|
28
|
+
- `app/robots.ts`, `app/sitemap.ts`, `app/opengraph-image.tsx`
|
|
29
|
+
- Metadata helpers come from `vista` / `vista/metadata`. OG images use `vista/og`.
|
|
30
|
+
- `vista g seo` adds `app/manifest.ts` when it is missing.
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# CLI
|
|
2
|
+
|
|
3
|
+
## Create an app
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx create-vista-app@latest my-app
|
|
7
|
+
npx create-vista-app@latest my-app --engine flashpack
|
|
8
|
+
npx create-vista-app@latest my-app --no-agents-md
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The scaffold writes `AGENTS.md` and `CLAUDE.md` unless `--no-agents-md` is set. `CLAUDE.md` is `@AGENTS.md`.
|
|
12
|
+
|
|
13
|
+
## Generators
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
vista g api-init
|
|
17
|
+
vista g router <name>
|
|
18
|
+
vista g procedure <name> [get|post]
|
|
19
|
+
vista g agent <name>
|
|
20
|
+
vista g auth
|
|
21
|
+
vista g seo
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `vista g agent <name>` writes `app/agents/<name>/agent.ts`, `app/api/agents/<name>/route.ts`, and `app/AGENTS.md` if that file is missing.
|
|
25
|
+
- `vista g auth` writes `auth.ts`, `app/api/auth/[...vista]/route.ts`, `/signin`, `/account`, fail-closed `middleware.ts`, a `SessionProvider` wrapper, and `.env.example`. Set `AUTH_SECRET` before signing in. Import the server helper from `vista/auth` and the client helper from `vista/auth/react`.
|
|
26
|
+
- `vista g api-init` starts the typed API. Call procedures from a Server Component with `v.createCaller(router, { ctx, env })`.
|
|
27
|
+
- `vista g seo` writes `app/robots.ts`, `app/sitemap.ts`, and `app/manifest.ts`.
|
|
28
|
+
|
|
29
|
+
`vista deploy` writes platform files. The scaffold does not copy Docker, Render, Netlify, Vercel, or Cloudflare configs.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Vista docs for agents
|
|
2
|
+
|
|
3
|
+
These files ship with the installed `vista` package and match that version. Read the guide that fits the task before writing code. Do not invent Next.js App Router APIs, `app/page.tsx` as the home route, or `app/layout.tsx`.
|
|
4
|
+
|
|
5
|
+
| Guide | Use it for |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| [app.md](app.md) | Routes, layouts, client components, `vista.config.ts`, build output |
|
|
8
|
+
| [cli.md](cli.md) | `create-vista-app` and `vista g` generators |
|
|
9
|
+
| [ai.md](ai.md) | Agents, tools, streaming, model strings |
|
|
10
|
+
| [rag.md](rag.md) | `InMemoryVectorStore`, `createRetrieverTool`, `embedText` |
|
|
11
|
+
|
|
12
|
+
Workflow skills (read the matching `SKILL.md` before a multi-step task):
|
|
13
|
+
|
|
14
|
+
- `../skills/vista-dev-loop/SKILL.md`
|
|
15
|
+
- `../skills/vista-agent/SKILL.md`
|
|
16
|
+
- `../skills/vista-rag/SKILL.md`
|
|
17
|
+
|
|
18
|
+
Imports in an app are `vista/...`. The published package name is `@vistagenic/vista`.
|
package/docs/rag.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# RAG
|
|
2
|
+
|
|
3
|
+
Index documents, retrieve matches, and pass them to an agent as a tool. Imports:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { agent, InMemoryVectorStore, createRetrieverTool, embedText } from 'vista/ai';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Keyword search
|
|
10
|
+
|
|
11
|
+
Omit `embed`. The retriever uses keyword search and needs no API key. `mock:echo` is enough for a smoke test.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const store = new InMemoryVectorStore();
|
|
15
|
+
store.addDocuments([
|
|
16
|
+
{ id: 'rsc', text: 'Vista uses React Server Components by default under app/.' },
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const search = createRetrieverTool({
|
|
20
|
+
name: 'search_knowledge_base',
|
|
21
|
+
description: 'Search the product knowledge base',
|
|
22
|
+
store,
|
|
23
|
+
topK: 3,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const ragAgent = agent({
|
|
27
|
+
name: 'rag',
|
|
28
|
+
model: 'mock:echo',
|
|
29
|
+
systemPrompt: 'Answer using search_knowledge_base results only.',
|
|
30
|
+
tools: [search],
|
|
31
|
+
memory: true,
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Embeddings
|
|
36
|
+
|
|
37
|
+
`embedText` calls an OpenAI-compatible `/embeddings` endpoint. Set the provider key. Pass the same embedder to the store and the tool.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const text = 'Vista builds into .vista/, similar to Next.js .next/.';
|
|
41
|
+
store.addDocument({
|
|
42
|
+
id: 'build',
|
|
43
|
+
text,
|
|
44
|
+
vector: await embedText(text, { model: 'openai:text-embedding-3-small' }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const search = createRetrieverTool({
|
|
48
|
+
store,
|
|
49
|
+
embed: (query) => embedText(query, { model: 'openai:text-embedding-3-small' }),
|
|
50
|
+
topK: 3,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Stream the agent from `app/api/agents/<name>/route.ts` and render it with `useAgent` from `vista/ai/react`. A runnable demo lives in the Vista repo at `apps/vista-rag-demo`.
|
|
Binary file
|